diff --git a/toolsrc/include/vcpkg_Dependencies.h b/toolsrc/include/vcpkg_Dependencies.h index 1c49ccafdc..528abedfb8 100644 --- a/toolsrc/include/vcpkg_Dependencies.h +++ b/toolsrc/include/vcpkg_Dependencies.h @@ -30,9 +30,23 @@ namespace vcpkg::Dependencies { NOT_INSTALLED, DEPENDENCIES_NOT_SATISFIED, - SHOULD_REMOVE + REMOVE, + REMOVE_USER_REQUESTED }; + struct remove_plan_action + { + remove_plan_type type; + std::unique_ptr bpgh; + }; + + struct package_spec_with_remove_plan + { + package_spec spec; + remove_plan_action plan; + }; std::vector create_install_plan(const vcpkg_paths& paths, const std::vector& specs, const StatusParagraphs& status_db); + + std::vector create_remove_plan(const vcpkg_paths& paths, const std::vector& specs, const StatusParagraphs& status_db); } diff --git a/toolsrc/src/commands_remove.cpp b/toolsrc/src/commands_remove.cpp index bf415d2c08..7b8286f9cc 100644 --- a/toolsrc/src/commands_remove.cpp +++ b/toolsrc/src/commands_remove.cpp @@ -7,6 +7,7 @@ namespace vcpkg::Commands::Remove { + using Dependencies::package_spec_with_remove_plan; using Dependencies::remove_plan_type; static const std::string OPTION_PURGE = "--purge"; @@ -57,7 +58,7 @@ namespace vcpkg::Commands::Remove if (!dependencies_out.empty()) return remove_plan_type::DEPENDENCIES_NOT_SATISFIED; - return remove_plan_type::SHOULD_REMOVE; + return remove_plan_type::REMOVE; } static void deinstall_package(const vcpkg_paths& paths, const package_spec& spec, StatusParagraphs& status_db) @@ -85,7 +86,7 @@ namespace vcpkg::Commands::Remove System::println(" %s depends on %s", dep->package.displayname(), pkg.package.displayname()); } exit(EXIT_FAILURE); - case remove_plan_type::SHOULD_REMOVE: + case remove_plan_type::REMOVE: break; default: Checks::unreachable(); @@ -174,6 +175,9 @@ namespace vcpkg::Commands::Remove Input::check_triplets(specs, paths); bool alsoRemoveFolderFromPackages = options.find(OPTION_PURGE) != options.end(); + const std::vector remove_plan = Dependencies::create_remove_plan(paths, specs, status_db); + Checks::check_exit(!remove_plan.empty(), "Remove plan cannot be empty"); + for (const package_spec& spec : specs) { deinstall_package(paths, spec, status_db); diff --git a/toolsrc/src/vcpkg_Dependencies.cpp b/toolsrc/src/vcpkg_Dependencies.cpp index 1daa6de02b..4468930c43 100644 --- a/toolsrc/src/vcpkg_Dependencies.cpp +++ b/toolsrc/src/vcpkg_Dependencies.cpp @@ -5,6 +5,7 @@ #include "package_spec.h" #include "StatusParagraphs.h" #include +#include #include "vcpkg_Maps.h" #include "vcpkg_Files.h" #include "vcpkglib.h" @@ -72,4 +73,61 @@ namespace vcpkg::Dependencies } return ret; } + + std::vector create_remove_plan(const vcpkg_paths& paths, const std::vector& specs, const StatusParagraphs& status_db) + { + std::unordered_set specs_as_set(specs.cbegin(), specs.cend()); + + std::unordered_map was_examined; // Examine = we have checked its immediate (non-recursive) dependencies + Graphs::Graph graph; + graph.add_vertices(specs); + + std::vector examine_stack(specs); + while (!examine_stack.empty()) + { + const package_spec spec = examine_stack.back(); + examine_stack.pop_back(); + + if (was_examined.find(spec) != was_examined.end()) + { + continue; + } + + auto it = status_db.find(spec); + if (it == status_db.end() || (*it)->state == install_state_t::not_installed) + { + was_examined.emplace(spec, remove_plan_action{ remove_plan_type::NOT_INSTALLED, nullptr}); + continue; + } + + for (const std::unique_ptr& an_installed_package : status_db) + { + if (an_installed_package->want != want_t::install) + continue; + if (an_installed_package->package.spec.target_triplet() != spec.target_triplet()) + continue; + + const std::vector& deps = an_installed_package->package.depends; + if (std::find(deps.begin(), deps.end(), spec.name()) == deps.end()) + { + continue; + } + + graph.add_edge(spec, an_installed_package.get()->package.spec); + examine_stack.push_back(an_installed_package.get()->package.spec); + } + + const remove_plan_type type = specs_as_set.find(spec) != specs_as_set.end() ? remove_plan_type::REMOVE_USER_REQUESTED: remove_plan_type::REMOVE; + was_examined.emplace(spec, remove_plan_action{ type, std::make_unique(std::move((*it)->package))}); + } + + std::vector ret; + + const std::vector pkgs = graph.find_topological_sort(); + for (const package_spec& pkg : pkgs) + { + ret.push_back({ pkg, std::move(was_examined[pkg]) }); + } + return ret; + } }