diff options
| author | Robert Schumacher <roschuma@microsoft.com> | 2017-08-22 15:14:59 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-08-22 15:14:59 -0700 |
| commit | 651ab5cef2c9742869185a181e1db529dc937d21 (patch) | |
| tree | d9b2239e97a6351c1a6a28cf7c8cf0cadf8c54e2 /toolsrc/src | |
| parent | 6035c3228bcef651d342b3a31827157ad2ed6a85 (diff) | |
| parent | 92dd1b77ed043da376c86874aacc1233270fedae (diff) | |
| download | vcpkg-651ab5cef2c9742869185a181e1db529dc937d21.tar.gz vcpkg-651ab5cef2c9742869185a181e1db529dc937d21.zip | |
Merge pull request #1566 from Microsoft/feature_package_end_to_end
end to end hdf5 feature packages
Diffstat (limited to 'toolsrc/src')
| -rw-r--r-- | toolsrc/src/BinaryParagraph.cpp | 12 | ||||
| -rw-r--r-- | toolsrc/src/PackageSpec.cpp | 165 | ||||
| -rw-r--r-- | toolsrc/src/Paragraphs.cpp | 16 | ||||
| -rw-r--r-- | toolsrc/src/SourceParagraph.cpp | 87 | ||||
| -rw-r--r-- | toolsrc/src/StatusParagraphs.cpp | 26 | ||||
| -rw-r--r-- | toolsrc/src/VcpkgPaths.cpp | 1 | ||||
| -rw-r--r-- | toolsrc/src/commands_depends.cpp | 4 | ||||
| -rw-r--r-- | toolsrc/src/commands_install.cpp | 323 | ||||
| -rw-r--r-- | toolsrc/src/commands_remove.cpp | 27 | ||||
| -rw-r--r-- | toolsrc/src/commands_search.cpp | 2 | ||||
| -rw-r--r-- | toolsrc/src/test_install_plan.cpp | 547 | ||||
| -rw-r--r-- | toolsrc/src/tests_dependencies.cpp | 17 | ||||
| -rw-r--r-- | toolsrc/src/tests_package_spec.cpp | 119 | ||||
| -rw-r--r-- | toolsrc/src/tests_paragraph.cpp | 56 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg_Build.cpp | 46 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg_Dependencies.cpp | 423 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg_Parse.cpp | 33 | ||||
| -rw-r--r-- | toolsrc/src/vcpkglib.cpp | 2 |
18 files changed, 1190 insertions, 716 deletions
diff --git a/toolsrc/src/BinaryParagraph.cpp b/toolsrc/src/BinaryParagraph.cpp index 49e9d58e5..9abd388b9 100644 --- a/toolsrc/src/BinaryParagraph.cpp +++ b/toolsrc/src/BinaryParagraph.cpp @@ -87,7 +87,17 @@ namespace vcpkg this->depends = filter_dependencies(fpgh.depends, triplet); } - std::string BinaryParagraph::displayname() const { return this->spec.to_string(); } + std::string BinaryParagraph::displayname() const + { + if (this->feature == "") + { + return this->spec.name() + "[core]:" + this->spec.triplet().to_string(); + } + else + { + return this->spec.name() + "[" + this->feature + "]:" + this->spec.triplet().to_string(); + } + } std::string BinaryParagraph::dir() const { return this->spec.dir(); } diff --git a/toolsrc/src/PackageSpec.cpp b/toolsrc/src/PackageSpec.cpp index a7e5648cd..a43bc5ff5 100644 --- a/toolsrc/src/PackageSpec.cpp +++ b/toolsrc/src/PackageSpec.cpp @@ -1,8 +1,11 @@ #include "pch.h" #include "PackageSpec.h" +#include "vcpkg_Parse.h" #include "vcpkg_Util.h" +using vcpkg::Parse::parse_comma_list; + namespace vcpkg { static bool is_valid_package_spec_char(char c) @@ -10,55 +13,69 @@ namespace vcpkg return (c == '-') || isdigit(c) || (isalpha(c) && islower(c)) || (c == '[') || (c == ']'); } - ExpectedT<FullPackageSpec, PackageSpecParseResult> FullPackageSpec::from_string(const std::string& spec_as_string, - const Triplet& default_triplet) + std::string FeatureSpec::to_string() const { - auto pos = spec_as_string.find(':'); - auto pos_l_bracket = spec_as_string.find('['); - auto pos_r_bracket = spec_as_string.find(']'); + if (feature().empty()) return spec().to_string(); + return Strings::format("%s[%s]:%s", name(), feature(), triplet()); + } - FullPackageSpec f; - if (pos == std::string::npos && pos_l_bracket == std::string::npos) - { - 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) + std::vector<FeatureSpec> FeatureSpec::from_strings_and_triplet(const std::vector<std::string>& depends, + const Triplet& triplet) + { + std::vector<FeatureSpec> f_specs; + for (auto&& depend : depends) { - if (pos_r_bracket == std::string::npos || pos_l_bracket >= pos_r_bracket) + auto maybe_spec = ParsedSpecifier::from_string(depend); + if (auto spec = maybe_spec.get()) { - return PackageSpecParseResult::INVALID_CHARACTERS; + Checks::check_exit(VCPKG_LINE_INFO, + spec->triplet.empty(), + "error: triplets cannot currently be specified in this context: %s", + depend); + PackageSpec pspec = + PackageSpec::from_name_and_triplet(spec->name, triplet).value_or_exit(VCPKG_LINE_INFO); + + for (auto&& feature : spec->features) + f_specs.push_back(FeatureSpec{pspec, feature}); + + if (spec->features.empty()) f_specs.push_back(FeatureSpec{pspec, ""}); } - 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) + else { - return PackageSpecParseResult::INVALID_CHARACTERS; + Checks::exit_with_message(VCPKG_LINE_INFO, + "error while parsing feature list: %s: %s", + vcpkg::to_string(maybe_spec.error()), + depend); } - 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); } + return f_specs; + } - auto pos2 = spec_as_string.find(':', pos + 1); - if (pos2 != std::string::npos) + std::vector<FeatureSpec> FullPackageSpec::to_feature_specs(const std::vector<FullPackageSpec>& specs) + { + std::vector<FeatureSpec> ret; + for (auto&& spec : specs) { - return PackageSpecParseResult::TOO_MANY_COLONS; + ret.emplace_back(spec.package_spec, ""); + for (auto&& feature : spec.features) + ret.emplace_back(spec.package_spec, feature); } - return f; + return ret; + } + + ExpectedT<FullPackageSpec, PackageSpecParseResult> FullPackageSpec::from_string(const std::string& spec_as_string, + const Triplet& default_triplet) + { + auto res = ParsedSpecifier::from_string(spec_as_string); + if (auto p = res.get()) + { + FullPackageSpec fspec; + Triplet t = p->triplet.empty() ? default_triplet : Triplet::from_canonical_name(p->triplet); + fspec.package_spec = PackageSpec::from_name_and_triplet(p->name, t).value_or_exit(VCPKG_LINE_INFO); + fspec.features = std::move(p->features); + return fspec; + } + return res.error(); } ExpectedT<PackageSpec, PackageSpecParseResult> PackageSpec::from_name_and_triplet(const std::string& name, @@ -81,11 +98,7 @@ namespace vcpkg std::string PackageSpec::dir() const { return Strings::format("%s_%s", this->m_name, this->m_triplet); } - std::string PackageSpec::to_string(const std::string& name, const Triplet& triplet) - { - return Strings::format("%s:%s", name, triplet); - } - std::string PackageSpec::to_string() const { return to_string(this->name(), this->triplet()); } + std::string PackageSpec::to_string() const { return Strings::format("%s:%s", this->name(), this->triplet()); } bool operator==(const PackageSpec& left, const PackageSpec& right) { @@ -93,4 +106,70 @@ namespace vcpkg } bool operator!=(const PackageSpec& left, const PackageSpec& right) { return !(left == right); } + + ExpectedT<ParsedSpecifier, PackageSpecParseResult> ParsedSpecifier::from_string(const std::string& input) + { + auto pos = input.find(':'); + auto pos_l_bracket = input.find('['); + auto pos_r_bracket = input.find(']'); + + ParsedSpecifier f; + if (pos == std::string::npos && pos_l_bracket == std::string::npos) + { + f.name = input; + 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 = input.substr(0, pos_l_bracket); + f.name = name; + f.features = parse_comma_list(input.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 = input.substr(0, pos); + f.triplet = input.substr(pos + 1); + f.name = name; + } + else + { + if (pos_r_bracket == std::string::npos || pos_l_bracket >= pos_r_bracket) + { + return PackageSpecParseResult::INVALID_CHARACTERS; + } + const std::string name = input.substr(0, pos_l_bracket); + f.features = parse_comma_list(input.substr(pos_l_bracket + 1, pos_r_bracket - pos_l_bracket - 1)); + f.triplet = input.substr(pos + 1); + f.name = name; + } + + auto pos2 = input.find(':', pos + 1); + if (pos2 != std::string::npos) + { + return PackageSpecParseResult::TOO_MANY_COLONS; + } + return f; + } + + ExpectedT<Features, PackageSpecParseResult> Features::from_string(const std::string& name) + { + auto maybe_spec = ParsedSpecifier::from_string(name); + if (auto spec = maybe_spec.get()) + { + Checks::check_exit( + VCPKG_LINE_INFO, spec->triplet.empty(), "error: triplet not allowed in specifier: %s", name); + + Features f; + f.name = spec->name; + f.features = spec->features; + return f; + } + + return maybe_spec.error(); + } } diff --git a/toolsrc/src/Paragraphs.cpp b/toolsrc/src/Paragraphs.cpp index 3749e919e..3a30f66a3 100644 --- a/toolsrc/src/Paragraphs.cpp +++ b/toolsrc/src/Paragraphs.cpp @@ -3,6 +3,7 @@ #include "ParagraphParseResult.h" #include "Paragraphs.h" #include "vcpkg_Files.h" +#include "vcpkg_Util.h" using namespace vcpkg::Parse; @@ -226,14 +227,21 @@ namespace vcpkg::Paragraphs return error_info; } - Expected<BinaryParagraph> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec) + Expected<BinaryControlFile> try_load_cached_control_package(const VcpkgPaths& paths, const PackageSpec& spec) { - Expected<std::unordered_map<std::string, std::string>> pghs = - get_single_paragraph(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL"); + Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = + get_paragraphs(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL"); if (auto p = pghs.get()) { - return BinaryParagraph(*p); + BinaryControlFile bcf; + bcf.core_paragraph = BinaryParagraph(p->front()); + p->erase(p->begin()); + + bcf.features = + Util::fmap(*p, [&](auto&& raw_feature) -> BinaryParagraph { return BinaryParagraph(raw_feature); }); + + return bcf; } return pghs.error(); diff --git a/toolsrc/src/SourceParagraph.cpp b/toolsrc/src/SourceParagraph.cpp index a37567f3a..54f34cbd3 100644 --- a/toolsrc/src/SourceParagraph.cpp +++ b/toolsrc/src/SourceParagraph.cpp @@ -1,5 +1,6 @@ #include "pch.h" +#include "PackageSpec.h" #include "SourceParagraph.h" #include "Triplet.h" #include "vcpkg_Checks.h" @@ -28,7 +29,11 @@ namespace vcpkg static span<const std::string> get_list_of_valid_fields() { static const std::string valid_fields[] = { - Fields::SOURCE, Fields::VERSION, Fields::DESCRIPTION, Fields::MAINTAINER, Fields::BUILD_DEPENDS, + Fields::SOURCE, + Fields::VERSION, + Fields::DESCRIPTION, + Fields::MAINTAINER, + Fields::BUILD_DEPENDS, }; return valid_fields; @@ -152,57 +157,52 @@ namespace vcpkg return std::move(control_file); } + Dependency Dependency::parse_dependency(std::string name, std::string qualifier) + { + Dependency dep; + dep.qualifier = qualifier; + if (auto maybe_features = Features::from_string(name)) + dep.depend = *maybe_features.get(); + else + Checks::exit_with_message( + VCPKG_LINE_INFO, "error while parsing dependency: %s: %s", to_string(maybe_features.error()), name); + return dep; + } + + std::string Dependency::name() const + { + std::string str = this->depend.name; + if (this->depend.features.empty()) return str; + + str += "["; + for (auto&& s : this->depend.features) + { + str += s + ","; + } + str.pop_back(); + str += "]"; + return str; + } + std::vector<Dependency> vcpkg::expand_qualified_dependencies(const std::vector<std::string>& depends) { return Util::fmap(depends, [&](const std::string& depend_string) -> Dependency { auto pos = depend_string.find(' '); - if (pos == std::string::npos) return {depend_string, ""}; + if (pos == std::string::npos) return Dependency::parse_dependency(depend_string, ""); // expect of the form "\w+ \[\w+\]" Dependency dep; - dep.name = depend_string.substr(0, pos); + + dep.depend.name = depend_string.substr(0, pos); if (depend_string.c_str()[pos + 1] != '(' || depend_string[depend_string.size() - 1] != ')') { // Error, but for now just slurp the entire string. - return {depend_string, ""}; + return Dependency::parse_dependency(depend_string, ""); } dep.qualifier = depend_string.substr(pos + 2, depend_string.size() - pos - 3); return dep; }); } - std::vector<std::string> parse_comma_list(const std::string& str) - { - if (str.empty()) - { - return {}; - } - - std::vector<std::string> out; - - size_t cur = 0; - do - { - auto pos = str.find(',', cur); - if (pos == std::string::npos) - { - out.push_back(str.substr(cur)); - break; - } - out.push_back(str.substr(cur, pos - cur)); - - // skip comma and space - ++pos; - if (str[pos] == ' ') - { - ++pos; - } - - cur = pos; - } while (cur != std::string::npos); - - return out; - } - std::vector<std::string> filter_dependencies(const std::vector<vcpkg::Dependency>& deps, const Triplet& t) { std::vector<std::string> ret; @@ -210,13 +210,22 @@ namespace vcpkg { if (dep.qualifier.empty() || t.canonical_name().find(dep.qualifier) != std::string::npos) { - ret.push_back(dep.name); + ret.emplace_back(dep.name()); } } return ret; } - const std::string& to_string(const Dependency& dep) { return dep.name; } + std::vector<FeatureSpec> filter_dependencies_to_specs(const std::vector<Dependency>& deps, const Triplet& t) + { + return FeatureSpec::from_strings_and_triplet(filter_dependencies(deps, t), t); + } + + const std::string to_string(const Dependency& dep) + { + std::string name = dep.name(); + return name; + } ExpectedT<Supports, std::vector<std::string>> Supports::parse(const std::vector<std::string>& strs) { diff --git a/toolsrc/src/StatusParagraphs.cpp b/toolsrc/src/StatusParagraphs.cpp index 27f3c30a2..02ee61f75 100644 --- a/toolsrc/src/StatusParagraphs.cpp +++ b/toolsrc/src/StatusParagraphs.cpp @@ -27,6 +27,30 @@ namespace vcpkg }); } + std::vector<std::unique_ptr<StatusParagraph>*> StatusParagraphs::find_all(const std::string& name, + const Triplet& triplet) + { + std::vector<std::unique_ptr<StatusParagraph>*> spghs; + for (auto&& p : *this) + { + if (p->package.spec.name() == name && p->package.spec.triplet() == triplet) + { + spghs.emplace_back(&p); + } + } + return spghs; + } + + StatusParagraphs::iterator StatusParagraphs::find(const std::string& name, + const Triplet& triplet, + const std::string& feature) + { + return std::find_if(begin(), end(), [&](const std::unique_ptr<StatusParagraph>& pgh) { + const PackageSpec& spec = pgh->package.spec; + return spec.name() == name && spec.triplet() == triplet && pgh->package.feature == feature; + }); + } + StatusParagraphs::const_iterator StatusParagraphs::find_installed(const std::string& name, const Triplet& triplet) const { @@ -43,7 +67,7 @@ namespace vcpkg { Checks::check_exit(VCPKG_LINE_INFO, pgh != nullptr, "Inserted null paragraph"); const PackageSpec& spec = pgh->package.spec; - auto ptr = find(spec.name(), spec.triplet()); + auto ptr = find(spec.name(), spec.triplet(), pgh->package.feature); if (ptr == end()) { paragraphs.push_back(std::move(pgh)); diff --git a/toolsrc/src/VcpkgPaths.cpp b/toolsrc/src/VcpkgPaths.cpp index 60204bcdd..4be636650 100644 --- a/toolsrc/src/VcpkgPaths.cpp +++ b/toolsrc/src/VcpkgPaths.cpp @@ -214,6 +214,7 @@ namespace vcpkg fs::path VcpkgPaths::package_dir(const PackageSpec& spec) const { return this->packages / spec.dir(); } fs::path VcpkgPaths::port_dir(const PackageSpec& spec) const { return this->ports / spec.name(); } + fs::path VcpkgPaths::port_dir(const std::string& name) const { return this->ports / name; } fs::path VcpkgPaths::build_info_file_path(const PackageSpec& spec) const { diff --git a/toolsrc/src/commands_depends.cpp b/toolsrc/src/commands_depends.cpp index 2d1fb658b..49e1c6c01 100644 --- a/toolsrc/src/commands_depends.cpp +++ b/toolsrc/src/commands_depends.cpp @@ -33,7 +33,7 @@ namespace vcpkg::Commands::DependInfo for (const Dependency& dependency : source_paragraph.depends) { - if (Strings::case_insensitive_ascii_contains(dependency.name, filter)) + if (Strings::case_insensitive_ascii_contains(dependency.name(), filter)) { return false; } @@ -46,7 +46,7 @@ namespace vcpkg::Commands::DependInfo for (auto&& source_control_file : source_control_files) { const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; - auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name; }); + auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name(); }); System::println("%s: %s", source_paragraph.name, s); } diff --git a/toolsrc/src/commands_install.cpp b/toolsrc/src/commands_install.cpp index 2ce5b6c62..9e37dc057 100644 --- a/toolsrc/src/commands_install.cpp +++ b/toolsrc/src/commands_install.cpp @@ -13,11 +13,7 @@ namespace vcpkg::Commands::Install { - using Dependencies::InstallPlanAction; - using Dependencies::RequestType; - using Dependencies::InstallPlanType; - using Dependencies::RemovePlanAction; - using Dependencies::RemovePlanType; + using namespace Dependencies; InstallDir InstallDir::from_destination_root(const fs::path& destination_root, const std::string& destination_subdirectory, @@ -176,45 +172,10 @@ namespace vcpkg::Commands::Install return SortedVector<std::string>(std::move(installed_files)); } - static void print_plan(const std::map<InstallPlanType, std::vector<const InstallPlanAction*>>& group_by_plan_type) + void install_package(const VcpkgPaths& paths, const BinaryControlFile& bcf, StatusParagraphs* status_db) { - static constexpr std::array<InstallPlanType, 3> order = { - InstallPlanType::ALREADY_INSTALLED, InstallPlanType::BUILD_AND_INSTALL, InstallPlanType::INSTALL}; - - for (const InstallPlanType plan_type : order) - { - auto it = group_by_plan_type.find(plan_type); - if (it == group_by_plan_type.cend()) - { - continue; - } - - std::vector<const InstallPlanAction*> cont = it->second; - std::sort(cont.begin(), cont.end(), &InstallPlanAction::compare_by_name); - const std::string as_string = Strings::join("\n", cont, [](const InstallPlanAction* p) { - return Dependencies::to_output_string(p->request_type, p->spec.to_string()); - }); - - switch (plan_type) - { - case InstallPlanType::ALREADY_INSTALLED: - System::println("The following packages are already installed:\n%s", as_string); - continue; - case InstallPlanType::BUILD_AND_INSTALL: - System::println("The following packages will be built and installed:\n%s", as_string); - continue; - case InstallPlanType::INSTALL: - System::println("The following packages will be installed:\n%s", as_string); - continue; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - } - - void install_package(const VcpkgPaths& paths, const BinaryParagraph& binary_paragraph, StatusParagraphs* status_db) - { - const fs::path package_dir = paths.package_dir(binary_paragraph.spec); - const Triplet& triplet = binary_paragraph.spec.triplet(); + const fs::path package_dir = paths.package_dir(bcf.core_paragraph.spec); + const Triplet& triplet = bcf.core_paragraph.spec.triplet(); const std::vector<StatusParagraphAndAssociatedFiles> pgh_and_files = get_installed_files(paths, *status_db); const SortedVector<std::string> package_files = @@ -234,7 +195,7 @@ namespace vcpkg::Commands::Install System::println(System::Color::error, "The following files are already installed in %s and are in conflict with %s", triplet_install_path.generic_string(), - binary_paragraph.spec); + bcf.core_paragraph.spec); System::print("\n "); System::println(Strings::join("\n ", intersection)); System::println(""); @@ -242,27 +203,42 @@ namespace vcpkg::Commands::Install } StatusParagraph source_paragraph; - source_paragraph.package = binary_paragraph; + source_paragraph.package = bcf.core_paragraph; source_paragraph.want = Want::INSTALL; source_paragraph.state = InstallState::HALF_INSTALLED; - for (auto&& dep : source_paragraph.package.depends) - { - if (status_db->find_installed(dep, source_paragraph.package.spec.triplet()) == status_db->end()) - { - Checks::unreachable(VCPKG_LINE_INFO); - } - } + write_update(paths, source_paragraph); status_db->insert(std::make_unique<StatusParagraph>(source_paragraph)); + std::vector<StatusParagraph> features_spghs; + for (auto&& feature : bcf.features) + { + features_spghs.emplace_back(); + + StatusParagraph& feature_paragraph = features_spghs.back(); + feature_paragraph.package = feature; + feature_paragraph.want = Want::INSTALL; + feature_paragraph.state = InstallState::HALF_INSTALLED; + + write_update(paths, feature_paragraph); + status_db->insert(std::make_unique<StatusParagraph>(feature_paragraph)); + } + const InstallDir install_dir = InstallDir::from_destination_root( - paths.installed, triplet.to_string(), paths.listfile_path(binary_paragraph)); + paths.installed, triplet.to_string(), paths.listfile_path(bcf.core_paragraph)); install_files_and_write_listfile(paths.get_filesystem(), package_dir, install_dir); source_paragraph.state = InstallState::INSTALLED; write_update(paths, source_paragraph); status_db->insert(std::make_unique<StatusParagraph>(source_paragraph)); + + for (auto&& feature_paragraph : features_spghs) + { + feature_paragraph.state = InstallState::INSTALLED; + write_update(paths, feature_paragraph); + status_db->insert(std::make_unique<StatusParagraph>(feature_paragraph)); + } } using Build::BuildResult; @@ -312,8 +288,8 @@ namespace vcpkg::Commands::Install } 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); + const BinaryControlFile bpgh = + Paragraphs::try_load_cached_control_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); @@ -322,10 +298,11 @@ namespace vcpkg::Commands::Install if (plan_type == InstallPlanType::BUILD_AND_INSTALL && g_feature_packages) { + const std::string display_name_feature = action.displayname(); if (use_head_version) - System::println("Building package %s from HEAD... ", display_name); + System::println("Building package %s from HEAD... ", display_name_feature); else - System::println("Building package %s... ", display_name); + System::println("Building package %s... ", display_name_feature); const Build::BuildPackageConfig build_config{ *action.any_paragraph.source_control_file.value_or_exit(VCPKG_LINE_INFO), @@ -339,13 +316,13 @@ namespace vcpkg::Commands::Install System::println(System::Color::error, Build::create_error_message(result.code, action.spec)); return result.code; } - System::println("Building package %s... done", display_name); + System::println("Building package %s... done", display_name_feature); - 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); + const BinaryControlFile bcf = + Paragraphs::try_load_cached_control_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO); + System::println("Installing package %s... ", display_name_feature); + install_package(paths, bcf, &status_db); + System::println(System::Color::success, "Installing package %s... done", display_name_feature); return BuildResult::SUCCEEDED; } @@ -357,7 +334,9 @@ namespace vcpkg::Commands::Install System::Color::warning, "Package %s is already built -- not building from HEAD", display_name); } System::println("Installing package %s... ", display_name); - install_package(paths, action.any_paragraph.binary_paragraph.value_or_exit(VCPKG_LINE_INFO), &status_db); + install_package(paths, + BinaryControlFile{action.any_paragraph.binary_paragraph.value_or_exit(VCPKG_LINE_INFO)}, + &status_db); System::println(System::Color::success, "Installing package %s... done", display_name); return BuildResult::SUCCEEDED; } @@ -365,72 +344,226 @@ namespace vcpkg::Commands::Install Checks::unreachable(VCPKG_LINE_INFO); } + static void print_plan(const std::vector<AnyAction>& action_plan, bool is_recursive) + { + std::vector<const RemovePlanAction*> remove_plans; + std::vector<const InstallPlanAction*> rebuilt_plans; + std::vector<const InstallPlanAction*> only_install_plans; + std::vector<const InstallPlanAction*> new_plans; + + const bool has_non_user_requested_packages = Util::find_if(action_plan, [](const AnyAction& package) -> bool { + if (auto iplan = package.install_plan.get()) + return iplan->request_type != RequestType::USER_REQUESTED; + else + return false; + }) != action_plan.cend(); + + for (auto&& action : action_plan) + { + if (auto install_action = action.install_plan.get()) + { + // remove plans are guaranteed to come before install plans, so we know the plan will be contained if at + // all. + auto it = Util::find_if( + remove_plans, [&](const RemovePlanAction* plan) { return plan->spec == install_action->spec; }); + if (it != remove_plans.end()) + { + rebuilt_plans.emplace_back(install_action); + } + else + { + if (install_action->plan_type == InstallPlanType::INSTALL) + only_install_plans.emplace_back(install_action); + else + new_plans.emplace_back(install_action); + } + } + else if (auto remove_action = action.remove_plan.get()) + { + remove_plans.emplace_back(remove_action); + } + } + + std::sort(remove_plans.begin(), remove_plans.end(), &RemovePlanAction::compare_by_name); + std::sort(rebuilt_plans.begin(), rebuilt_plans.end(), &InstallPlanAction::compare_by_name); + std::sort(only_install_plans.begin(), only_install_plans.end(), &InstallPlanAction::compare_by_name); + std::sort(new_plans.begin(), new_plans.end(), &InstallPlanAction::compare_by_name); + + const std::string rebuilt_string = Strings::join("\n", rebuilt_plans, [](const InstallPlanAction* p) { + return to_output_string(p->request_type, p->displayname()); + }); + if (rebuilt_plans.size() > 0) System::println("The following packages will be rebuilt:\n%s", rebuilt_string); + + const std::string new_string = Strings::join("\n", new_plans, [](const InstallPlanAction* p) { + return to_output_string(p->request_type, p->displayname()); + }); + if (new_plans.size() > 0) + System::println("The following packages will be built and installed:\n%s", new_string); + + const std::string only_install_string = Strings::join("\n", only_install_plans, [](const InstallPlanAction* p) { + return to_output_string(p->request_type, p->displayname()); + }); + if (only_install_plans.size() > 0) + System::println("The following packages will be directly installed:\n%s", only_install_string); + + if (has_non_user_requested_packages) + System::println("Additional packages (*) will be installed to complete this operation."); + + if (remove_plans.size() > 0 && !is_recursive) + { + System::println(System::Color::warning, + "If you are sure you want to rebuild the above packages, run the command with the " + "--recurse option"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) { static const std::string OPTION_DRY_RUN = "--dry-run"; static const std::string OPTION_USE_HEAD_VERSION = "--head"; static const std::string OPTION_NO_DOWNLOADS = "--no-downloads"; + static const std::string OPTION_RECURSE = "--recurse"; // input sanitization static const std::string example = Commands::Help::create_example_string("install zlib zlib:x64-windows curl boost"); args.check_min_arg_count(1, example); - const std::vector<PackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { - return Input::check_and_get_package_spec(arg, default_triplet, example); + const std::vector<FullPackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { + return Input::check_and_get_full_package_spec(arg, default_triplet, example); }); + for (auto&& spec : specs) - Input::check_triplet(spec.triplet(), paths); + { + Input::check_triplet(spec.package_spec.triplet(), paths); + if (!spec.features.empty() && !g_feature_packages) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Feature packages are experimentally available under the --featurepackages flag."); + } + } const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments( - {OPTION_DRY_RUN, OPTION_USE_HEAD_VERSION, OPTION_NO_DOWNLOADS}); + {OPTION_DRY_RUN, OPTION_USE_HEAD_VERSION, OPTION_NO_DOWNLOADS, OPTION_RECURSE}); const bool dryRun = options.find(OPTION_DRY_RUN) != options.cend(); const bool use_head_version = options.find(OPTION_USE_HEAD_VERSION) != options.cend(); const bool no_downloads = options.find(OPTION_NO_DOWNLOADS) != options.cend(); + const bool is_recursive = options.find(OPTION_RECURSE) != options.cend(); // create the plan StatusParagraphs status_db = database_load_check(paths); - Dependencies::PathsPortFile paths_port_file(paths); - std::vector<InstallPlanAction> 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 - const std::string specs_string = - Strings::join(",", install_plan, [](const InstallPlanAction& plan) { return plan.spec.to_string(); }); - Metrics::track_property("installplan", specs_string); - - std::map<InstallPlanType, std::vector<const InstallPlanAction*>> group_by_plan_type; - Util::group_by(install_plan, &group_by_plan_type, [](const InstallPlanAction& p) { return p.plan_type; }); - print_plan(group_by_plan_type); + const Build::BuildPackageOptions install_plan_options = {Build::to_use_head_version(use_head_version), + Build::to_allow_downloads(!no_downloads)}; - const bool has_non_user_requested_packages = - Util::find_if(install_plan, [](const InstallPlanAction& package) -> bool { - return package.request_type != RequestType::USER_REQUESTED; - }) != install_plan.cend(); + std::vector<AnyAction> action_plan; - if (has_non_user_requested_packages) + if (g_feature_packages) { - System::println("Additional packages (*) will be installed to complete this operation."); + std::unordered_map<std::string, SourceControlFile> scf_map; + auto all_ports = Paragraphs::try_load_all_ports(paths.get_filesystem(), paths.ports); + for (auto&& port : all_ports.paragraphs) + { + scf_map[port->core_paragraph->name] = std::move(*port); + } + action_plan = create_feature_install_plan(scf_map, FullPackageSpec::to_feature_specs(specs), status_db); + } + else + { + Dependencies::PathsPortFile paths_port_file(paths); + auto install_plan = Dependencies::create_install_plan( + paths_port_file, Util::fmap(specs, [](auto&& spec) { return spec.package_spec; }), status_db); + + action_plan = Util::fmap( + install_plan, [](InstallPlanAction& install_action) { return AnyAction(std::move(install_action)); }); } + // install plan will be empty if it is already installed - need to change this at status paragraph part + Checks::check_exit(VCPKG_LINE_INFO, !action_plan.empty(), "Install plan cannot be empty"); + + // log the plan + const std::string specs_string = Strings::join(",", action_plan, [](const AnyAction& action) { + if (auto iaction = action.install_plan.get()) + return iaction->spec.to_string(); + else if (auto raction = action.remove_plan.get()) + return "R$" + raction->spec.to_string(); + Checks::unreachable(VCPKG_LINE_INFO); + }); + Metrics::track_property("installplan", specs_string); + + print_plan(action_plan, is_recursive); + if (dryRun) { Checks::exit_success(VCPKG_LINE_INFO); } - const Build::BuildPackageOptions install_plan_options = {Build::to_use_head_version(use_head_version), - Build::to_allow_downloads(!no_downloads)}; - // execute the plan - for (const InstallPlanAction& action : install_plan) + if (g_feature_packages) + { + for (const auto& action : action_plan) + { + if (auto install_action = action.install_plan.get()) + { + const BuildResult result = + perform_install_plan_action(paths, *install_action, install_plan_options, status_db); + if (result != BuildResult::SUCCEEDED) + { + System::println(Build::create_user_troubleshooting_message(install_action->spec)); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + else if (auto remove_action = action.remove_plan.get()) + { + static const std::string OPTION_PURGE = "--purge"; + static const std::string OPTION_NO_PURGE = "--no-purge"; + + const bool alsoRemoveFolderFromPackages = options.find(OPTION_NO_PURGE) == options.end(); + if (options.find(OPTION_PURGE) != options.end() && !alsoRemoveFolderFromPackages) + { + // User specified --purge and --no-purge + System::println(System::Color::error, "Error: cannot specify both --no-purge and --purge."); + System::print(example); + Checks::exit_fail(VCPKG_LINE_INFO); + } + const std::string display_name = remove_action->spec.to_string(); + switch (remove_action->plan_type) + { + case RemovePlanType::NOT_INSTALLED: + System::println(System::Color::success, "Package %s is not installed", display_name); + break; + case RemovePlanType::REMOVE: + System::println("Removing package %s... ", display_name); + Commands::Remove::remove_package(paths, remove_action->spec, &status_db); + System::println(System::Color::success, "Removing package %s... done", display_name); + break; + case RemovePlanType::UNKNOWN: + default: Checks::unreachable(VCPKG_LINE_INFO); + } + + if (alsoRemoveFolderFromPackages) + { + System::println("Purging package %s... ", display_name); + Files::Filesystem& fs = paths.get_filesystem(); + std::error_code ec; + fs.remove_all(paths.packages / remove_action->spec.dir(), ec); + System::println(System::Color::success, "Purging package %s... done", display_name); + } + } + } + } + else { - const BuildResult result = perform_install_plan_action(paths, action, install_plan_options, status_db); - if (result != BuildResult::SUCCEEDED) + for (const auto& action : action_plan) { - System::println(Build::create_user_troubleshooting_message(action.spec)); - Checks::exit_fail(VCPKG_LINE_INFO); + const auto& iaction = action.install_plan.value_or_exit(VCPKG_LINE_INFO); + const BuildResult result = perform_install_plan_action(paths, iaction, install_plan_options, status_db); + if (result != BuildResult::SUCCEEDED) + { + System::println(Build::create_user_troubleshooting_message(iaction.spec)); + Checks::exit_fail(VCPKG_LINE_INFO); + } } } diff --git a/toolsrc/src/commands_remove.cpp b/toolsrc/src/commands_remove.cpp index eabf2b9ae..e480f02dd 100644 --- a/toolsrc/src/commands_remove.cpp +++ b/toolsrc/src/commands_remove.cpp @@ -17,13 +17,19 @@ namespace vcpkg::Commands::Remove 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()); + auto spghs = status_db->find_all(spec.name(), spec.triplet()); + auto core_pkg = **status_db->find(spec.name(), spec.triplet(), ""); - pkg.want = Want::PURGE; - pkg.state = InstallState::HALF_INSTALLED; - write_update(paths, pkg); + for (auto&& spgh : spghs) + { + StatusParagraph& pkg = **spgh; + if (pkg.state != InstallState::INSTALLED) continue; + pkg.want = Want::PURGE; + pkg.state = InstallState::HALF_INSTALLED; + write_update(paths, pkg); + } - auto maybe_lines = fs.read_lines(paths.listfile_path(pkg.package)); + auto maybe_lines = fs.read_lines(paths.listfile_path(core_pkg.package)); if (auto lines = maybe_lines.get()) { @@ -80,11 +86,16 @@ namespace vcpkg::Commands::Remove } } - fs.remove(paths.listfile_path(pkg.package)); + fs.remove(paths.listfile_path(core_pkg.package)); } - pkg.state = InstallState::NOT_INSTALLED; - write_update(paths, pkg); + for (auto&& spgh : spghs) + { + StatusParagraph& pkg = **spgh; + if (pkg.state != InstallState::HALF_INSTALLED) continue; + pkg.state = InstallState::NOT_INSTALLED; + write_update(paths, pkg); + } } static void print_plan(const std::map<RemovePlanType, std::vector<const RemovePlanAction*>>& group_by_plan_type) diff --git a/toolsrc/src/commands_search.cpp b/toolsrc/src/commands_search.cpp index fee99a5db..f12c25fb6 100644 --- a/toolsrc/src/commands_search.cpp +++ b/toolsrc/src/commands_search.cpp @@ -39,7 +39,7 @@ namespace vcpkg::Commands::Search s.append(Strings::format("%s;", name)); for (const Dependency& d : source_paragraph.depends) { - const std::string dependency_name = replace_dashes_with_underscore(d.name); + const std::string dependency_name = replace_dashes_with_underscore(d.name()); s.append(Strings::format("%s -> %s;", name, dependency_name)); } } diff --git a/toolsrc/src/test_install_plan.cpp b/toolsrc/src/test_install_plan.cpp index d02af5662..6c9311264 100644 --- a/toolsrc/src/test_install_plan.cpp +++ b/toolsrc/src/test_install_plan.cpp @@ -6,32 +6,62 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace vcpkg; -namespace UnitTest1 +namespace Microsoft::VisualStudio::CppUnitTestFramework { - class InstallPlanTests : public TestClass<InstallPlanTests> + template<> + inline std::wstring ToString<vcpkg::Dependencies::InstallPlanType>(const vcpkg::Dependencies::InstallPlanType& t) { - struct PackageSpecMap + switch (t) { - std::unordered_map<PackageSpec, SourceControlFile> map; - Triplet triplet; - PackageSpecMap(const Triplet& t) { triplet = t; } + case Dependencies::InstallPlanType::ALREADY_INSTALLED: return L"ALREADY_INSTALLED"; + case Dependencies::InstallPlanType::BUILD_AND_INSTALL: return L"BUILD_AND_INSTALL"; + case Dependencies::InstallPlanType::INSTALL: return L"INSTALL"; + case Dependencies::InstallPlanType::UNKNOWN: return L"UNKNOWN"; + default: return ToString((int)t); + } + } - PackageSpec get_package_spec(std::vector<std::unordered_map<std::string, std::string>>&& fields) - { - auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(fields)); - Assert::IsTrue(m_pgh.has_value()); - auto& scf = *m_pgh.get(); + template<> + inline std::wstring ToString<vcpkg::Dependencies::RequestType>(const vcpkg::Dependencies::RequestType& t) + { + switch (t) + { + case Dependencies::RequestType::AUTO_SELECTED: return L"AUTO_SELECTED"; + case Dependencies::RequestType::USER_REQUESTED: return L"USER_REQUESTED"; + case Dependencies::RequestType::UNKNOWN: return L"UNKNOWN"; + default: return ToString((int)t); + } + } +} - 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) +namespace UnitTest1 +{ + class InstallPlanTests : public TestClass<InstallPlanTests> + { + static std::unique_ptr<SourceControlFile> make_control_file( + const char* name, + const char* depends, + const std::vector<std::pair<const char*, const char*>>& 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}, + }); + for (auto&& feature : features) { - return get_package_spec({{{"Source", source}, {"Version", version}, {"Build-Depends", build_depends}}}); + scf_pghs.push_back(Pgh{ + {"Feature", feature.first}, + {"Description", "feature"}, + {"Build-Depends", feature.second}, + }); } - }; + auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(scf_pghs)); + Assert::IsTrue(m_pgh.has_value()); + return std::move(*m_pgh.get()); + } static void features_check(Dependencies::AnyAction* install_action, std::string pkg_name, @@ -68,16 +98,60 @@ namespace UnitTest1 Assert::AreEqual(pkg_name.c_str(), plan.spec.name().c_str()); } + static std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, const char* depends = "") + { + using Pgh = std::unordered_map<std::string, std::string>; + return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, + {"Version", "1"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", depends}, + {"Status", "install ok installed"}}); + } + static std::unique_ptr<StatusParagraph> make_status_feature_pgh(const char* name, + const char* feature, + const char* depends = "") + { + using Pgh = std::unordered_map<std::string, std::string>; + return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, + {"Version", "1"}, + {"Feature", feature}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", depends}, + {"Status", "install ok installed"}}); + } + struct PackageSpecMap + { + std::unordered_map<std::string, SourceControlFile> map; + Triplet triplet; + PackageSpecMap(const Triplet& t) { triplet = t; } + + PackageSpec emplace(const char* name, + const char* depends = "", + const std::vector<std::pair<const char*, const char*>>& features = {}) + { + return emplace(std::move(*make_control_file(name, depends, features))); + } + PackageSpec emplace(vcpkg::SourceControlFile&& scf) + { + auto spec = PackageSpec::from_name_and_triplet(scf.core_paragraph->name, triplet); + Assert::IsTrue(spec.has_value()); + map.emplace(scf.core_paragraph->name, std::move(scf)); + return PackageSpec{*spec.get()}; + } + }; + TEST_METHOD(basic_install_scheme) { std::vector<std::unique_ptr<StatusParagraph>> 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 spec_a = spec_map.emplace("a", "b"); + auto spec_b = spec_map.emplace("b", "c"); + auto spec_c = spec_map.emplace("c"); - auto map_port = Dependencies::MapPortFile(spec_map.map); + Dependencies::MapPortFile map_port(spec_map.map); auto install_plan = Dependencies::create_install_plan(map_port, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); @@ -92,16 +166,16 @@ namespace UnitTest1 std::vector<std::unique_ptr<StatusParagraph>> 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 spec_a = spec_map.emplace("a", "d"); + auto spec_b = spec_map.emplace("b", "d, e"); + auto spec_c = spec_map.emplace("c", "e, h"); + auto spec_d = spec_map.emplace("d", "f, g, h"); + auto spec_e = spec_map.emplace("e", "g"); + auto spec_f = spec_map.emplace("f"); + auto spec_g = spec_map.emplace("g"); + auto spec_h = spec_map.emplace("h"); + + Dependencies::MapPortFile map_port(spec_map.map); auto install_plan = Dependencies::create_install_plan( map_port, {spec_a, spec_b, spec_c}, StatusParagraphs(std::move(status_paragraphs))); @@ -127,37 +201,74 @@ namespace UnitTest1 Assert::IsTrue(e_pos > g_pos); } + TEST_METHOD(existing_package_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = FullPackageSpec{spec_map.emplace("a")}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(1), install_plan.size()); + auto p = install_plan[0].install_plan.get(); + Assert::IsNotNull(p); + Assert::AreEqual("a", p->spec.name().c_str()); + Assert::AreEqual(Dependencies::InstallPlanType::ALREADY_INSTALLED, p->plan_type); + Assert::AreEqual(Dependencies::RequestType::USER_REQUESTED, p->request_type); + } + + TEST_METHOD(user_requested_package_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(2), install_plan.size()); + auto p = install_plan[0].install_plan.get(); + Assert::IsNotNull(p); + Assert::AreEqual("b", p->spec.name().c_str()); + Assert::AreEqual(Dependencies::InstallPlanType::BUILD_AND_INSTALL, p->plan_type); + Assert::AreEqual(Dependencies::RequestType::AUTO_SELECTED, p->request_type); + + auto p2 = install_plan[1].install_plan.get(); + Assert::IsNotNull(p2); + Assert::AreEqual("a", p2->spec.name().c_str()); + Assert::AreEqual(Dependencies::InstallPlanType::BUILD_AND_INSTALL, p2->plan_type); + Assert::AreEqual(Dependencies::RequestType::USER_REQUESTED, p2->request_type); + } + TEST_METHOD(long_install_scheme) { - using Pgh = std::unordered_map<std::string, std::string>; std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(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<StatusParagraph>(Pgh{{"Package", "k"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); + status_paragraphs.push_back(make_status_pgh("j", "k")); + status_paragraphs.push_back(make_status_pgh("k")); 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 spec_a = spec_map.emplace("a", "b, c, d, e, f, g, h, j, k"); + auto spec_b = spec_map.emplace("b", "c, d, e, f, g, h, j, k"); + auto spec_c = spec_map.emplace("c", "d, e, f, g, h, j, k"); + auto spec_d = spec_map.emplace("d", "e, f, g, h, j, k"); + auto spec_e = spec_map.emplace("e", "f, g, h, j, k"); + auto spec_f = spec_map.emplace("f", "g, h, j, k"); + auto spec_g = spec_map.emplace("g", "h, j, k"); + auto spec_h = spec_map.emplace("h", "j, k"); + auto spec_j = spec_map.emplace("j", "k"); + auto spec_k = spec_map.emplace("k"); + + Dependencies::MapPortFile map_port(spec_map.map); auto install_plan = Dependencies::create_install_plan(map_port, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); @@ -174,162 +285,85 @@ namespace UnitTest1 TEST_METHOD(basic_feature_test_1) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(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<StatusParagraph>(Pgh{{"Package", "b"}, - {"Feature", "beefeatureone"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); - status_paragraphs.push_back(std::make_unique<StatusParagraph>(Pgh{{"Package", "b"}, - {"Default-Features", "beefeatureone"}, - {"Version", "1.3"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); + status_paragraphs.push_back(make_status_pgh("a", "b, b[b1]")); + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.push_back(make_status_feature_pgh("b", "b1")); 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 spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1]", {{"a1", "b[b2]"}}), {"a1"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({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"}); + features_check(&install_plan[2], "b", {"b1", "core", "b1"}); + features_check(&install_plan[3], "a", {"a1", "core"}); } TEST_METHOD(basic_feature_test_2) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> 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 spec_a = FullPackageSpec{spec_map.emplace("a", "b[b1]", {{"a1", "b[b2]"}}), {"a1"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({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"}); + features_check(&install_plan[0], "b", {"b1", "b2", "core"}); + features_check(&install_plan[1], "a", {"a1", "core"}); } TEST_METHOD(basic_feature_test_3) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(Pgh{{"Package", "a"}, - {"Default-Features", ""}, - {"Version", "1.3"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); + status_paragraphs.push_back(make_status_pgh("a")); 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 spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_c, spec_a}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({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[2], "a", {"a1", "core"}); features_check(&install_plan[3], "c", {"core"}); } TEST_METHOD(basic_feature_test_4) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(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<StatusParagraph>(Pgh{{"Package", "a"}, - {"Feature", "one"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.push_back(make_status_feature_pgh("a", "a1", "")); 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 spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}})}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_c}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_c}), + StatusParagraphs(std::move(status_paragraphs))); Assert::AreEqual(size_t(1), install_plan.size()); features_check(&install_plan[0], "c", {"core"}); @@ -337,168 +371,93 @@ namespace UnitTest1 TEST_METHOD(basic_feature_test_5) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> 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 spec_a = + FullPackageSpec{spec_map.emplace("a", "", {{"a1", "b[b1]"}, {"a2", "b[b2]"}, {"a3", "a[a2]"}}), {"a3"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}})}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({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"}); + features_check(&install_plan[0], "b", {"core", "b2"}); + features_check(&install_plan[1], "a", {"core", "a3", "a2"}); } TEST_METHOD(basic_feature_test_6) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(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); + status_paragraphs.push_back(make_status_pgh("b")); - 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"}}; + PackageSpecMap spec_map(Triplet::X86_WINDOWS); + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[core]"), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_a, spec_b}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({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[1], "b", {"core", "b1"}); features_check(&install_plan[2], "a", {"core"}); } TEST_METHOD(basic_feature_test_7) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(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<StatusParagraph>(Pgh{{"Package", "b"}, - {"Default-Features", ""}, - {"Version", "1.3"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); + status_paragraphs.push_back(make_status_pgh("x", "b")); + status_paragraphs.push_back(make_status_pgh("b")); + 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 spec_a = FullPackageSpec{spec_map.emplace("a")}; + auto spec_x = FullPackageSpec{spec_map.emplace("x", "a"), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {spec_b, spec_x}, StatusParagraphs(std::move(status_paragraphs))); + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_b}), + 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"}); + features_check(&install_plan[2], "b", {"core", "b1"}); + features_check(&install_plan[3], "a", {"core"}); + features_check(&install_plan[4], "x", {"core"}); } TEST_METHOD(basic_feature_test_8) { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(std::make_unique<StatusParagraph>(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<StatusParagraph>(Pgh{{"Package", "a"}, - {"Default-Features", ""}, - {"Version", "1.3"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", ""}, - {"Status", "install ok installed"}})); + status_paragraphs.push_back(make_status_pgh("a")); + 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); 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"}}; + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"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 spec_a_86 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b_86 = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c_86 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"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))); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({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 { @@ -517,10 +476,10 @@ namespace UnitTest1 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[3], "a", {"a1", "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[6], "a", {"a1", "core"}); features_check(&install_plan[7], "c", {"core"}); } }; diff --git a/toolsrc/src/tests_dependencies.cpp b/toolsrc/src/tests_dependencies.cpp index 7a49bdbd0..6a6981d73 100644 --- a/toolsrc/src/tests_dependencies.cpp +++ b/toolsrc/src/tests_dependencies.cpp @@ -8,6 +8,7 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace vcpkg; +using Parse::parse_comma_list; namespace UnitTest1 { @@ -17,7 +18,7 @@ namespace UnitTest1 { auto v = expand_qualified_dependencies(parse_comma_list("libA (windows)")); Assert::AreEqual(size_t(1), v.size()); - Assert::AreEqual("libA", v[0].name.c_str()); + Assert::AreEqual("libA", v[0].depend.name.c_str()); Assert::AreEqual("windows", v[0].qualifier.c_str()); } @@ -41,7 +42,15 @@ namespace UnitTest1 TEST_METHOD(parse_supports_all) { auto v = Supports::parse({ - "x64", "x86", "arm", "windows", "uwp", "v140", "v141", "crt-static", "crt-dynamic", + "x64", + "x86", + "arm", + "windows", + "uwp", + "v140", + "v141", + "crt-static", + "crt-dynamic", }); Assert::AreNotEqual(uintptr_t(0), uintptr_t(v.get())); @@ -74,7 +83,9 @@ namespace UnitTest1 TEST_METHOD(parse_supports_some) { auto v = Supports::parse({ - "x64", "x86", "windows", + "x64", + "x86", + "windows", }); Assert::AreNotEqual(uintptr_t(0), uintptr_t(v.get())); diff --git a/toolsrc/src/tests_package_spec.cpp b/toolsrc/src/tests_package_spec.cpp new file mode 100644 index 000000000..fa201b372 --- /dev/null +++ b/toolsrc/src/tests_package_spec.cpp @@ -0,0 +1,119 @@ +#include "BinaryParagraph.h" +#include "CppUnitTest.h" +#include "Paragraphs.h" +#include "vcpkg_Strings.h" + +#pragma comment(lib, "version") +#pragma comment(lib, "winhttp") + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace Microsoft::VisualStudio::CppUnitTestFramework +{ + template<> + inline std::wstring ToString<vcpkg::PackageSpecParseResult>(const vcpkg::PackageSpecParseResult& t) + { + return ToString(static_cast<uint32_t>(t)); + } + + template<> + inline std::wstring ToString<vcpkg::PackageSpec>(const vcpkg::PackageSpec& t) + { + return ToString(t.to_string()); + } +} + +namespace Strings = vcpkg::Strings; + +namespace UnitTest1 +{ + using namespace vcpkg; + + class SpecifierConversion : public TestClass<SpecifierConversion> + { + TEST_METHOD(full_package_spec_to_feature_specs) + { + auto a_spec = PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + auto b_spec = PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + auto fspecs = FullPackageSpec::to_feature_specs({{a_spec, {"0", "1"}}, {b_spec, {"2", "3"}}}); + + Assert::AreEqual(size_t(6), fspecs.size()); + + std::array<const char*, 6> features = {"", "0", "1", "", "2", "3"}; + std::array<PackageSpec*, 6> specs = {&a_spec, &a_spec, &a_spec, &b_spec, &b_spec, &b_spec}; + + for (int i = 0; i < features.size(); ++i) + { + Assert::AreEqual(features[i], fspecs[i].feature().c_str()); + Assert::AreEqual(*specs[i], fspecs[i].spec()); + } + } + }; + + class SpecifierParsing : public TestClass<SpecifierParsing> + { + TEST_METHOD(parsed_specifier_from_string) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::AreEqual(size_t(0), spec->features.size()); + Assert::AreEqual("", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_from_string_with_triplet) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib:x64-uwp"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::AreEqual("x64-uwp", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_from_string_with_colons) + { + auto ec = vcpkg::ParsedSpecifier::from_string("zlib:x86-uwp:").error(); + Assert::AreEqual(vcpkg::PackageSpecParseResult::TOO_MANY_COLONS, ec); + } + + TEST_METHOD(parsed_specifier_from_string_with_feature) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[feature]:x64-uwp"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::IsTrue(spec->features.size() == 1); + Assert::AreEqual("feature", spec->features.front().c_str()); + Assert::AreEqual("x64-uwp", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_from_string_with_many_features) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[0, 1,2]"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::IsTrue(spec->features.size() == 3); + Assert::AreEqual("0", spec->features[0].c_str()); + Assert::AreEqual("1", spec->features[1].c_str()); + Assert::AreEqual("2", spec->features[2].c_str()); + Assert::AreEqual("", spec->triplet.c_str()); + } + + TEST_METHOD(utf8_to_utf16) + { + auto str = vcpkg::Strings::to_utf16("abc"); + Assert::AreEqual(L"abc", str.c_str()); + } + + TEST_METHOD(utf8_to_utf16_with_whitespace) + { + auto str = vcpkg::Strings::to_utf16("abc -x86-windows"); + Assert::AreEqual(L"abc -x86-windows", str.c_str()); + } + }; + + TEST_CLASS(Metrics){}; +} diff --git a/toolsrc/src/tests_paragraph.cpp b/toolsrc/src/tests_paragraph.cpp index af4b55498..47a07e12d 100644 --- a/toolsrc/src/tests_paragraph.cpp +++ b/toolsrc/src/tests_paragraph.cpp @@ -60,7 +60,7 @@ namespace UnitTest1 Assert::AreEqual("m", pgh->core_paragraph->maintainer.c_str()); 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("bd", pgh->core_paragraph->depends[0].name().c_str()); Assert::AreEqual(size_t(1), pgh->core_paragraph->supports.size()); Assert::AreEqual("x64", pgh->core_paragraph->supports[0].c_str()); } @@ -77,8 +77,8 @@ namespace UnitTest1 auto& pgh = *m_pgh.get(); Assert::AreEqual(size_t(2), pgh->core_paragraph->depends.size()); - Assert::AreEqual("z", pgh->core_paragraph->depends[0].name.c_str()); - Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name.c_str()); + Assert::AreEqual("z", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name().c_str()); } TEST_METHOD(SourceParagraph_Three_Depends) @@ -93,9 +93,9 @@ namespace UnitTest1 auto& pgh = *m_pgh.get(); Assert::AreEqual(size_t(3), pgh->core_paragraph->depends.size()); - Assert::AreEqual("z", pgh->core_paragraph->depends[0].name.c_str()); - Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name.c_str()); - Assert::AreEqual("xyz", pgh->core_paragraph->depends[2].name.c_str()); + Assert::AreEqual("z", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name().c_str()); + Assert::AreEqual("xyz", pgh->core_paragraph->depends[2].name().c_str()); } TEST_METHOD(SourceParagraph_Three_Supports) @@ -131,9 +131,9 @@ namespace UnitTest1 Assert::AreEqual("", pgh->core_paragraph->maintainer.c_str()); Assert::AreEqual("", pgh->core_paragraph->description.c_str()); Assert::AreEqual(size_t(2), pgh->core_paragraph->depends.size()); - Assert::AreEqual("libA", pgh->core_paragraph->depends[0].name.c_str()); + Assert::AreEqual("libA", pgh->core_paragraph->depends[0].name().c_str()); Assert::AreEqual("windows", pgh->core_paragraph->depends[0].qualifier.c_str()); - Assert::AreEqual("libB", pgh->core_paragraph->depends[1].name.c_str()); + Assert::AreEqual("libB", pgh->core_paragraph->depends[1].name().c_str()); Assert::AreEqual("uwp", pgh->core_paragraph->depends[1].qualifier.c_str()); } @@ -384,45 +384,5 @@ namespace UnitTest1 Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual("a, b, c", pghs[0]["Depends"].c_str()); } - - TEST_METHOD(package_spec_parse) - { - vcpkg::ExpectedT<vcpkg::FullPackageSpec, vcpkg::PackageSpecParseResult> spec = - vcpkg::FullPackageSpec::from_string("zlib", vcpkg::Triplet::X86_WINDOWS); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, spec.error()); - 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<vcpkg::FullPackageSpec, vcpkg::PackageSpecParseResult> spec = - vcpkg::FullPackageSpec::from_string("zlib:x64-uwp", vcpkg::Triplet::X86_WINDOWS); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, spec.error()); - 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::FullPackageSpec::from_string("zlib:x86-uwp:", vcpkg::Triplet::X86_WINDOWS).error(); - Assert::AreEqual(vcpkg::PackageSpecParseResult::TOO_MANY_COLONS, ec); - } - - TEST_METHOD(utf8_to_utf16) - { - auto str = vcpkg::Strings::to_utf16("abc"); - Assert::AreEqual(L"abc", str.c_str()); - } - - TEST_METHOD(utf8_to_utf16_with_whitespace) - { - auto str = vcpkg::Strings::to_utf16("abc -x86-windows"); - Assert::AreEqual(L"abc -x86-windows", str.c_str()); - } }; - - TEST_CLASS(Metrics){}; } diff --git a/toolsrc/src/vcpkg_Build.cpp b/toolsrc/src/vcpkg_Build.cpp index a0d690f37..124efb7f2 100644 --- a/toolsrc/src/vcpkg_Build.cpp +++ b/toolsrc/src/vcpkg_Build.cpp @@ -88,29 +88,37 @@ namespace vcpkg::Build return Strings::wformat(LR"("%s" %s %s %s 2>&1)", toolset.vcvarsall.native(), arch, target, tonull); } - static void create_binary_control_file(const VcpkgPaths& paths, - const SourceParagraph& source_paragraph, + static void create_binary_feature_control_file(const SourceParagraph& source_paragraph, + const FeatureParagraph& feature_paragraph, + const Triplet& triplet, + BinaryControlFile& bcf) + { + BinaryParagraph bpgh(source_paragraph, feature_paragraph, triplet); + bcf.features.emplace_back(std::move(bpgh)); + } + + static void create_binary_control_file(const SourceParagraph& source_paragraph, const Triplet& triplet, - const BuildInfo& build_info) + const BuildInfo& build_info, + BinaryControlFile& bcf) { - BinaryParagraph bpgh = BinaryParagraph(source_paragraph, triplet); + BinaryParagraph bpgh(source_paragraph, triplet); if (auto p_ver = build_info.version.get()) { bpgh.version = *p_ver; } - const fs::path binary_control_file = paths.packages / bpgh.dir() / "CONTROL"; - paths.get_filesystem().write_contents(binary_control_file, Strings::serialize(bpgh)); + bcf.core_paragraph = std::move(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) + static void write_binary_control_file(const VcpkgPaths& paths, BinaryControlFile bcf) { - 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)); + std::string start = Strings::serialize(bcf.core_paragraph); + for (auto&& feature : bcf.features) + { + start += "\n" + Strings::serialize(feature); + } + const fs::path binary_control_file = paths.packages / bcf.core_paragraph.dir() / "CONTROL"; + paths.get_filesystem().write_contents(binary_control_file, start); } ExtendedBuildResult build_package(const VcpkgPaths& paths, @@ -196,6 +204,10 @@ namespace vcpkg::Build auto build_info = read_build_info(paths.get_filesystem(), paths.build_info_file_path(spec)); const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); + BinaryControlFile bcf; + + create_binary_control_file(config.src, triplet, build_info, bcf); + if (error_count != 0) { return {BuildResult::POST_BUILD_CHECKS_FAILED, {}}; @@ -209,13 +221,13 @@ namespace vcpkg::Build 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_feature_control_file(*config.scf->core_paragraph, *f_pgh, triplet, bcf); } } } } - create_binary_control_file(paths, config.src, triplet, build_info); + + write_binary_control_file(paths, bcf); // const fs::path port_buildtrees_dir = paths.buildtrees / spec.name; // delete_directory(port_buildtrees_dir); diff --git a/toolsrc/src/vcpkg_Dependencies.cpp b/toolsrc/src/vcpkg_Dependencies.cpp index 820e51b33..188b0f444 100644 --- a/toolsrc/src/vcpkg_Dependencies.cpp +++ b/toolsrc/src/vcpkg_Dependencies.cpp @@ -12,7 +12,99 @@ namespace vcpkg::Dependencies { + struct FeatureNodeEdges + { + std::vector<FeatureSpec> remove_edges; + std::vector<FeatureSpec> build_edges; + bool plus = false; + }; + + struct Cluster : Util::MoveOnlyBase + { + std::vector<StatusParagraph*> status_paragraphs; + Optional<const SourceControlFile*> source_control_file; + PackageSpec spec; + std::unordered_map<std::string, FeatureNodeEdges> edges; + std::unordered_set<std::string> to_install_features; + std::unordered_set<std::string> original_features; + bool will_remove = false; + bool transient_uninstalled = true; + RequestType request_type = RequestType::AUTO_SELECTED; + }; + + struct ClusterPtr + { + Cluster* ptr; + + Cluster* operator->() { return ptr; } + }; + bool operator==(const ClusterPtr& l, const ClusterPtr& r) { return l.ptr == r.ptr; } +} + +template<> +struct std::hash<vcpkg::Dependencies::ClusterPtr> +{ + size_t operator()(const vcpkg::Dependencies::ClusterPtr& value) const + { + return std::hash<vcpkg::PackageSpec>()(value.ptr->spec); + } +}; + +namespace vcpkg::Dependencies +{ + struct GraphPlan + { + Graphs::Graph<ClusterPtr> remove_graph; + Graphs::Graph<ClusterPtr> install_graph; + }; + + struct ClusterGraph : Util::MoveOnlyBase + { + explicit ClusterGraph(std::unordered_map<std::string, const SourceControlFile*>&& ports) + : m_ports(std::move(ports)) + { + } + + Cluster& get(const PackageSpec& spec) + { + auto it = m_graph.find(spec); + if (it == m_graph.end()) + { + // Load on-demand from m_ports + auto it_ports = m_ports.find(spec.name()); + if (it_ports != m_ports.end()) + { + auto& clust = m_graph[spec]; + clust.spec = spec; + cluster_from_scf(*it_ports->second, clust); + return clust; + } + return m_graph[spec]; + } + return it->second; + } + + private: + void cluster_from_scf(const SourceControlFile& scf, Cluster& out_cluster) + { + FeatureNodeEdges core_dependencies; + core_dependencies.build_edges = + filter_dependencies_to_specs(scf.core_paragraph->depends, out_cluster.spec.triplet()); + out_cluster.edges.emplace("core", std::move(core_dependencies)); + + for (const auto& feature : scf.feature_paragraphs) + { + FeatureNodeEdges added_edges; + added_edges.build_edges = filter_dependencies_to_specs(feature->depends, out_cluster.spec.triplet()); + out_cluster.edges.emplace(feature->name, std::move(added_edges)); + } + out_cluster.source_control_file = &scf; + } + + std::unordered_map<PackageSpec, Cluster> m_graph; + std::unordered_map<std::string, const SourceControlFile*> m_ports; + }; std::vector<PackageSpec> AnyParagraph::dependencies(const Triplet& triplet) const { @@ -51,56 +143,69 @@ namespace vcpkg::Dependencies } } - InstallPlanAction::InstallPlanAction() - : spec(), any_paragraph(), plan_type(InstallPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) - { - } + InstallPlanAction::InstallPlanAction() : plan_type(InstallPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) {} InstallPlanAction::InstallPlanAction(const PackageSpec& spec, const SourceControlFile& any_paragraph, const std::unordered_set<std::string>& features, const RequestType& request_type) - : InstallPlanAction() + : spec(spec), plan_type(InstallPlanType::BUILD_AND_INSTALL), request_type(request_type), feature_list(features) { - 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 std::unordered_set<std::string>& features, + const RequestType& request_type) + : spec(spec), plan_type(InstallPlanType::ALREADY_INSTALLED), request_type(request_type), feature_list(features) + { } InstallPlanAction::InstallPlanAction(const PackageSpec& spec, const AnyParagraph& any_paragraph, const RequestType& request_type) - : InstallPlanAction() + : spec(spec), request_type(request_type), any_paragraph(any_paragraph) { - this->spec = spec; - this->request_type = request_type; if (auto p = any_paragraph.status_paragraph.get()) { this->plan_type = InstallPlanType::ALREADY_INSTALLED; - this->any_paragraph.status_paragraph = *p; return; } if (auto p = any_paragraph.binary_paragraph.get()) { this->plan_type = InstallPlanType::INSTALL; - this->any_paragraph.binary_paragraph = *p; return; } if (auto p = any_paragraph.source_paragraph.get()) { this->plan_type = InstallPlanType::BUILD_AND_INSTALL; - this->any_paragraph.source_paragraph = *p; return; } this->plan_type = InstallPlanType::UNKNOWN; } + std::string InstallPlanAction::displayname() const + { + if (this->feature_list.empty()) + { + return this->spec.to_string(); + } + else + { + std::string features; + for (auto&& feature : this->feature_list) + { + features += feature + ","; + } + features.pop_back(); + + return this->spec.name() + "[" + features + "]:" + this->spec.triplet().to_string(); + } + } + bool InstallPlanAction::compare_by_name(const InstallPlanAction* left, const InstallPlanAction* right) { return left->spec.name() < right->spec.name(); @@ -155,9 +260,9 @@ namespace vcpkg::Dependencies return left->spec.name() < right->spec.name(); } - MapPortFile::MapPortFile(const std::unordered_map<PackageSpec, SourceControlFile>& map) : ports(map) {} + MapPortFile::MapPortFile(const std::unordered_map<std::string, SourceControlFile>& map) : ports(map) {} - const SourceControlFile& MapPortFile::get_control_file(const PackageSpec& spec) const + const SourceControlFile& MapPortFile::get_control_file(const std::string& spec) const { auto scf = ports.find(spec); if (scf == ports.end()) @@ -169,9 +274,9 @@ namespace vcpkg::Dependencies PathsPortFile::PathsPortFile(const VcpkgPaths& paths) : ports(paths) {} - const SourceControlFile& PathsPortFile::get_control_file(const PackageSpec& spec) const + const SourceControlFile& PathsPortFile::get_control_file(const std::string& spec) const { - std::unordered_map<PackageSpec, SourceControlFile>::iterator cache_it = cache.find(spec); + auto cache_it = cache.find(spec); if (cache_it != cache.end()) { return cache_it->second; @@ -219,7 +324,9 @@ namespace vcpkg::Dependencies auto it = status_db.find_installed(spec); if (it != status_db.end()) return InstallPlanAction{spec, {*it->get(), nullopt, nullopt}, request_type}; return InstallPlanAction{ - spec, {nullopt, nullopt, *port_file_provider.get_control_file(spec).core_paragraph}, request_type}; + spec, + {nullopt, nullopt, *port_file_provider.get_control_file(spec.name()).core_paragraph}, + request_type}; } }; @@ -319,9 +426,9 @@ namespace vcpkg::Dependencies ? RequestType::USER_REQUESTED : RequestType::AUTO_SELECTED; - Expected<BinaryParagraph> maybe_bpgh = Paragraphs::try_load_cached_package(paths, spec); - if (auto bpgh = maybe_bpgh.get()) - return ExportPlanAction{spec, {nullopt, *bpgh, nullopt}, request_type}; + Expected<BinaryControlFile> maybe_bpgh = Paragraphs::try_load_cached_control_package(paths, spec); + if (auto bcf = maybe_bpgh.get()) + return ExportPlanAction{spec, {nullopt, bcf->core_paragraph, nullopt}, request_type}; auto maybe_scf = Paragraphs::try_load_port(paths.get_filesystem(), paths.port_dir(spec)); if (auto scf = maybe_scf.get()) @@ -339,93 +446,79 @@ namespace vcpkg::Dependencies return toposort; } - std::vector<FeatureSpec> to_feature_specs(const std::vector<std::string>& depends, const Triplet& triplet) + enum class MarkPlusResult { - std::vector<FeatureSpec> 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); + FEATURE_NOT_FOUND, + SUCCESS, + }; - auto feature_spec = FeatureSpec{p_spec, ""}; - f_specs.emplace_back(std::move(feature_spec)); - } - } - return f_specs; - } + MarkPlusResult mark_plus(const std::string& feature, + Cluster& cluster, + ClusterGraph& pkg_to_cluster, + GraphPlan& graph_plan); + void mark_minus(Cluster& cluster, ClusterGraph& pkg_to_cluster, GraphPlan& graph_plan); - bool mark_plus(const std::string& feature, - Cluster& cluster, - std::unordered_map<PackageSpec, Cluster>& pkg_to_cluster, - GraphPlan& graph_plan) + MarkPlusResult mark_plus(const std::string& feature, Cluster& cluster, ClusterGraph& graph, GraphPlan& graph_plan) { - auto it = cluster.edges.find(feature); - std::string updated_feature = feature; - if (updated_feature == "") + if (feature == "") { - updated_feature = "core"; - it = cluster.edges.find("core"); - } - if (it == cluster.edges.end()) - { - Checks::unreachable(VCPKG_LINE_INFO); + // Indicates that core was not specified in the reference + return mark_plus("core", cluster, graph, graph_plan); } - if (cluster.edges[updated_feature].plus) return true; + auto it = cluster.edges.find(feature); + if (it == cluster.edges.end()) return MarkPlusResult::FEATURE_NOT_FOUND; + + if (cluster.edges[feature].plus) return MarkPlusResult::SUCCESS; - if (cluster.original_features.find(updated_feature) == cluster.original_features.end()) + if (cluster.original_features.find(feature) == cluster.original_features.end()) { cluster.transient_uninstalled = true; } if (!cluster.transient_uninstalled) { - return false; + return MarkPlusResult::SUCCESS; } - cluster.edges[updated_feature].plus = true; + cluster.edges[feature].plus = true; if (!cluster.original_features.empty()) { - mark_minus(cluster, pkg_to_cluster, graph_plan); + mark_minus(cluster, graph, 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()) + tracked.insert(feature); + + if (feature != "core") { - 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}); - } + // All features implicitly depend on core + auto res = mark_plus("core", cluster, graph, graph_plan); + + // Should be impossible for "core" to not exist + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); } - for (auto&& depend : cluster.edges[updated_feature].build_edges) + for (auto&& depend : cluster.edges[feature].build_edges) { - auto& depend_cluster = pkg_to_cluster[depend.spec]; - mark_plus(depend.feature_name, depend_cluster, pkg_to_cluster, graph_plan); + auto& depend_cluster = graph.get(depend.spec()); + auto res = mark_plus(depend.feature(), depend_cluster, graph, graph_plan); + + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: Unable to satisfy dependency %s of %s", + depend, + FeatureSpec(cluster.spec, feature)); + if (&depend_cluster == &cluster) continue; graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); } - return true; + + return MarkPlusResult::SUCCESS; } - void mark_minus(Cluster& cluster, std::unordered_map<PackageSpec, Cluster>& pkg_to_cluster, GraphPlan& graph_plan) + void mark_minus(Cluster& cluster, ClusterGraph& graph, GraphPlan& graph_plan) { if (cluster.will_remove) return; cluster.will_remove = true; @@ -436,89 +529,93 @@ namespace vcpkg::Dependencies auto& remove_edges_edges = pair.second.remove_edges; for (auto&& depend : remove_edges_edges) { - auto& depend_cluster = pkg_to_cluster[depend.spec]; + auto& depend_cluster = graph.get(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); + mark_minus(depend_cluster, graph, graph_plan); } } + + cluster.transient_uninstalled = true; for (auto&& original_feature : cluster.original_features) { - cluster.transient_uninstalled = true; - mark_plus(original_feature, cluster, pkg_to_cluster, graph_plan); + auto res = mark_plus(original_feature, cluster, graph, graph_plan); + if (res != MarkPlusResult::SUCCESS) + { + System::println(System::Color::warning, + "Warning: could not reinstall feature %s", + FeatureSpec{cluster.spec, original_feature}); + } } } - std::vector<AnyAction> create_feature_install_plan(const std::unordered_map<PackageSpec, SourceControlFile>& map, - const std::vector<FullPackageSpec>& specs, - const StatusParagraphs& status_db) + + static ClusterGraph create_feature_install_graph(const std::unordered_map<std::string, SourceControlFile>& map, + const StatusParagraphs& status_db) { - std::unordered_map<PackageSpec, Cluster> pkg_spec_to_package_node; + std::unordered_map<std::string, const SourceControlFile*> ptr_map; + for (auto&& p : map) + ptr_map.emplace(p.first, &p.second); + ClusterGraph graph(std::move(ptr_map)); + + auto installed_ports = get_installed_ports(status_db); - for (const auto& it : map) + for (auto&& status_paragraph : installed_ports) { - Cluster& node = pkg_spec_to_package_node[it.first]; + Cluster& cluster = graph.get(status_paragraph->package.spec); - 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); + cluster.transient_uninstalled = false; - for (const auto& feature : it.second.feature_paragraphs) + cluster.status_paragraphs.emplace_back(status_paragraph); + + auto& status_paragraph_feature = status_paragraph->package.feature; + // In this case, empty string indicates the "core" paragraph for a package. + if (status_paragraph_feature == "") { - 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)); + cluster.original_features.insert("core"); + } + else + { + cluster.original_features.insert(status_paragraph_feature); } - node.source_control_file = &it.second; } - for (auto&& status_paragraph : get_installed_ports(status_db)) + for (auto&& status_paragraph : installed_ports) { 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()); + auto reverse_edges = FeatureSpec::from_strings_and_triplet(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]; + auto& dep_cluster = graph.get(dependency.spec()); + + auto depends_name = dependency.feature(); + if (depends_name == "") depends_name = "core"; + + auto& target_node = dep_cluster.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); - } } + return graph; + } + + std::vector<AnyAction> create_feature_install_plan(const std::unordered_map<std::string, SourceControlFile>& map, + const std::vector<FeatureSpec>& specs, + const StatusParagraphs& status_db) + { + ClusterGraph graph = create_feature_install_graph(map, status_db); 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); - } + Cluster& spec_cluster = graph.get(spec.spec()); + spec_cluster.request_type = RequestType::USER_REQUESTED; + auto res = mark_plus(spec.feature(), spec_cluster, graph, graph_plan); + + Checks::check_exit( + VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); + + graph_plan.install_graph.add_vertex(ClusterPtr{&spec_cluster}); } Graphs::GraphAdjacencyProvider<ClusterPtr> adjacency_remove_graph(graph_plan.remove_graph.adjacency_list()); @@ -529,38 +626,46 @@ namespace vcpkg::Dependencies auto insert_vertex_list = graph_plan.install_graph.vertex_list(); auto insert_toposort = Graphs::topological_sort(insert_vertex_list, adjacency_install_graph); - std::vector<AnyAction> install_plan; + std::vector<AnyAction> plan; - for (auto&& like_cluster : remove_toposort) + for (auto&& p_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), + auto scf = *p_cluster->source_control_file.get(); + auto spec = PackageSpec::from_name_and_triplet(scf->core_paragraph->name, p_cluster->spec.triplet()) + .value_or_exit(VCPKG_LINE_INFO); + plan.emplace_back(RemovePlanAction{ + std::move(spec), RemovePlanType::REMOVE, - RequestType::AUTO_SELECTED}; - - install_plan.emplace_back(std::move(any_plan)); + p_cluster->request_type, + }); } - for (auto&& like_cluster : insert_toposort) + for (auto&& p_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)); + if (p_cluster->transient_uninstalled) + { + // If it will be transiently uninstalled, we need to issue a full installation command + auto pscf = p_cluster->source_control_file.value_or_exit(VCPKG_LINE_INFO); + Checks::check_exit(VCPKG_LINE_INFO, pscf != nullptr); + plan.emplace_back(InstallPlanAction{ + p_cluster->spec, + *pscf, + p_cluster->to_install_features, + p_cluster->request_type, + }); + } + else + { + // If the package isn't transitively installed, still include it if the user explicitly requested it + if (p_cluster->request_type != RequestType::USER_REQUESTED) continue; + plan.emplace_back(InstallPlanAction{ + p_cluster->spec, + p_cluster->original_features, + p_cluster->request_type, + }); + } } - return install_plan; + return plan; } } diff --git a/toolsrc/src/vcpkg_Parse.cpp b/toolsrc/src/vcpkg_Parse.cpp index 659af2939..118cde900 100644 --- a/toolsrc/src/vcpkg_Parse.cpp +++ b/toolsrc/src/vcpkg_Parse.cpp @@ -44,4 +44,37 @@ namespace vcpkg::Parse } return nullptr; } + + std::vector<std::string> parse_comma_list(const std::string& str) + { + if (str.empty()) + { + return {}; + } + + std::vector<std::string> out; + + size_t cur = 0; + do + { + auto pos = str.find(',', cur); + if (pos == std::string::npos) + { + out.push_back(str.substr(cur)); + break; + } + out.push_back(str.substr(cur, pos - cur)); + + // skip comma and space + ++pos; + if (str[pos] == ' ') + { + ++pos; + } + + cur = pos; + } while (cur != std::string::npos); + + return out; + } } diff --git a/toolsrc/src/vcpkglib.cpp b/toolsrc/src/vcpkglib.cpp index 6b180b532..428ae090d 100644 --- a/toolsrc/src/vcpkglib.cpp +++ b/toolsrc/src/vcpkglib.cpp @@ -191,7 +191,7 @@ namespace vcpkg for (const std::unique_ptr<StatusParagraph>& pgh : status_db) { - if (pgh->state != InstallState::INSTALLED) + if (pgh->state != InstallState::INSTALLED || pgh->package.feature != "") { continue; } |
