diff options
| author | Robert Schumacher <roschuma@microsoft.com> | 2020-02-04 15:50:10 -0800 |
|---|---|---|
| committer | Robert Schumacher <roschuma@microsoft.com> | 2020-02-04 15:50:10 -0800 |
| commit | 6f66ad14fe9da11d4bf50f5b25b4da86ed971c53 (patch) | |
| tree | 0f5dbcd1719cd6a8e486c4058cfefd607d58aa6c /toolsrc/src | |
| parent | d502f061bb3ee0258d6453acbf258b9e5d93d564 (diff) | |
| parent | d808514c9df44bb97d6eccff952bfe8ec4e156f7 (diff) | |
| download | vcpkg-6f66ad14fe9da11d4bf50f5b25b4da86ed971c53.tar.gz vcpkg-6f66ad14fe9da11d4bf50f5b25b4da86ed971c53.zip | |
Merge remote-tracking branch 'origin/master' into HEAD
Diffstat (limited to 'toolsrc/src')
38 files changed, 2551 insertions, 1684 deletions
diff --git a/toolsrc/src/vcpkg-test/dependencies.cpp b/toolsrc/src/vcpkg-test/dependencies.cpp index 2344bb990..63e7cfee7 100644 --- a/toolsrc/src/vcpkg-test/dependencies.cpp +++ b/toolsrc/src/vcpkg-test/dependencies.cpp @@ -1,5 +1,7 @@ #include <catch2/catch.hpp> - +#include <vcpkg-test/mockcmakevarprovider.h> +#include <vcpkg-test/util.h> +#include <vcpkg/dependencies.h> #include <vcpkg/sourceparagraph.h> using namespace vcpkg; @@ -7,22 +9,69 @@ using Parse::parse_comma_list; TEST_CASE ("parse depends", "[dependencies]") { - auto v = expand_qualified_dependencies(parse_comma_list("libA (windows)")); + auto v = expand_qualified_dependencies(parse_comma_list("liba (windows)")); REQUIRE(v.size() == 1); - REQUIRE(v.at(0).depend.name == "libA"); + REQUIRE(v.at(0).depend.name == "liba"); REQUIRE(v.at(0).qualifier == "windows"); } TEST_CASE ("filter depends", "[dependencies]") { - auto deps = expand_qualified_dependencies(parse_comma_list("libA (windows), libB, libC (uwp)")); - auto v = filter_dependencies(deps, Triplet::X64_WINDOWS); + const std::unordered_map<std::string, std::string> x64_win_cmake_vars{{"VCPKG_TARGET_ARCHITECTURE", "x64"}, + {"VCPKG_CMAKE_SYSTEM_NAME", ""}}; + + const std::unordered_map<std::string, std::string> arm_uwp_cmake_vars{{"VCPKG_TARGET_ARCHITECTURE", "arm"}, + {"VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"}}; + + auto deps = expand_qualified_dependencies(parse_comma_list("liba (windows), libb, libc (uwp)")); + auto v = filter_dependencies(deps, Triplet::X64_WINDOWS, x64_win_cmake_vars); REQUIRE(v.size() == 2); - REQUIRE(v.at(0) == "libA"); - REQUIRE(v.at(1) == "libB"); + REQUIRE(v.at(0).package_spec.name() == "liba"); + REQUIRE(v.at(1).package_spec.name() == "libb"); - auto v2 = filter_dependencies(deps, Triplet::ARM_UWP); + auto v2 = filter_dependencies(deps, Triplet::ARM_UWP, arm_uwp_cmake_vars); + REQUIRE(v.size() == 2); + REQUIRE(v2.at(0).package_spec.name() == "libb"); + REQUIRE(v2.at(1).package_spec.name() == "libc"); +} + +TEST_CASE ("parse feature depends", "[dependencies]") +{ + auto u = parse_comma_list("libwebp[anim, gif2webp, img2webp, info, mux, nearlossless, " + "simd, cwebp, dwebp], libwebp[vwebp_sdl, extras] (!osx)"); + REQUIRE(u.at(1) == "libwebp[vwebp_sdl, extras] (!osx)"); + auto v = expand_qualified_dependencies(u); REQUIRE(v.size() == 2); - REQUIRE(v2.at(0) == "libB"); - REQUIRE(v2.at(1) == "libC"); + auto&& a0 = v.at(0); + REQUIRE(a0.depend.name == "libwebp"); + REQUIRE(a0.depend.features.size() == 9); + REQUIRE(a0.qualifier.empty()); + + auto&& a1 = v.at(1); + REQUIRE(a1.depend.name == "libwebp"); + REQUIRE(a1.depend.features.size() == 2); + REQUIRE(a1.qualifier == "!osx"); +} + +TEST_CASE ("qualified dependency", "[dependencies]") +{ + using namespace Test; + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1] (linux)"), {}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {}}; + + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto plan = vcpkg::Dependencies::create_feature_install_plan(map_port, var_provider, {spec_a}, {}); + REQUIRE(plan.install_actions.size() == 2); + REQUIRE(plan.install_actions.at(0).feature_list == std::vector<std::string>{"core"}); + + FullPackageSpec linspec_a{PackageSpec::from_name_and_triplet("a", Triplet::from_canonical_name("x64-linux")) + .value_or_exit(VCPKG_LINE_INFO), + {}}; + var_provider.dep_info_vars[linspec_a.package_spec].emplace("VCPKG_CMAKE_SYSTEM_NAME", "Linux"); + auto plan2 = vcpkg::Dependencies::create_feature_install_plan(map_port, var_provider, {linspec_a}, {}); + REQUIRE(plan2.install_actions.size() == 2); + REQUIRE(plan2.install_actions.at(0).feature_list == std::vector<std::string>{"b1", "core"}); } diff --git a/toolsrc/src/vcpkg-test/mockcmakevarsprovider.cpp b/toolsrc/src/vcpkg-test/mockcmakevarsprovider.cpp new file mode 100644 index 000000000..eda1a7a64 --- /dev/null +++ b/toolsrc/src/vcpkg-test/mockcmakevarsprovider.cpp @@ -0,0 +1,28 @@ +#include <vcpkg-test/mockcmakevarprovider.h>
+
+namespace vcpkg::Test
+{
+ Optional<const std::unordered_map<std::string, std::string>&> MockCMakeVarProvider::get_generic_triplet_vars(
+ const Triplet& triplet) const
+ {
+ auto it = generic_triplet_vars.find(triplet);
+ if (it == generic_triplet_vars.end()) return nullopt;
+ return it->second;
+ }
+
+ Optional<const std::unordered_map<std::string, std::string>&> MockCMakeVarProvider::get_dep_info_vars(
+ const PackageSpec& spec) const
+ {
+ auto it = dep_info_vars.find(spec);
+ if (it == dep_info_vars.end()) return nullopt;
+ return it->second;
+ }
+
+ Optional<const std::unordered_map<std::string, std::string>&> MockCMakeVarProvider::get_tag_vars(
+ const PackageSpec& spec) const
+ {
+ auto it = tag_vars.find(spec);
+ if (it == tag_vars.end()) return nullopt;
+ return it->second;
+ }
+}
\ No newline at end of file diff --git a/toolsrc/src/vcpkg-test/paragraph.cpp b/toolsrc/src/vcpkg-test/paragraph.cpp index 85c37851d..2ee4efe2f 100644 --- a/toolsrc/src/vcpkg-test/paragraph.cpp +++ b/toolsrc/src/vcpkg-test/paragraph.cpp @@ -10,10 +10,11 @@ namespace Strings = vcpkg::Strings; TEST_CASE ("SourceParagraph construct minimum", "[paragraph]") { auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - }}); + vcpkg::SourceControlFile::parse_control_file("", + std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + }}); REQUIRE(m_pgh.has_value()); auto& pgh = **m_pgh.get(); @@ -28,15 +29,15 @@ TEST_CASE ("SourceParagraph construct minimum", "[paragraph]") TEST_CASE ("SourceParagraph construct maximum", "[paragraph]") { auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "s"}, - {"Version", "v"}, - {"Maintainer", "m"}, - {"Description", "d"}, - {"Build-Depends", "bd"}, - {"Default-Features", "df"}, - {"Supports", "x64"}, - }}); + vcpkg::SourceControlFile::parse_control_file("", + std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "s"}, + {"Version", "v"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Build-Depends", "bd"}, + {"Default-Features", "df"}, + }}); REQUIRE(m_pgh.has_value()); auto& pgh = **m_pgh.get(); @@ -48,18 +49,17 @@ TEST_CASE ("SourceParagraph construct maximum", "[paragraph]") REQUIRE(pgh.core_paragraph->depends[0].name() == "bd"); REQUIRE(pgh.core_paragraph->default_features.size() == 1); REQUIRE(pgh.core_paragraph->default_features[0] == "df"); - REQUIRE(pgh.core_paragraph->supports.size() == 1); - REQUIRE(pgh.core_paragraph->supports[0] == "x64"); } TEST_CASE ("SourceParagraph two depends", "[paragraph]") { auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Build-Depends", "z, openssl"}, - }}); + vcpkg::SourceControlFile::parse_control_file("", + std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl"}, + }}); REQUIRE(m_pgh.has_value()); auto& pgh = **m_pgh.get(); @@ -71,11 +71,12 @@ TEST_CASE ("SourceParagraph two depends", "[paragraph]") TEST_CASE ("SourceParagraph three depends", "[paragraph]") { auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Build-Depends", "z, openssl, xyz"}, - }}); + vcpkg::SourceControlFile::parse_control_file("", + std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl, xyz"}, + }}); REQUIRE(m_pgh.has_value()); auto& pgh = **m_pgh.get(); @@ -85,31 +86,15 @@ TEST_CASE ("SourceParagraph three depends", "[paragraph]") REQUIRE(pgh.core_paragraph->depends[2].name() == "xyz"); } -TEST_CASE ("SourceParagraph three supports", "[paragraph]") -{ - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Supports", "x64, windows, uwp"}, - }}); - REQUIRE(m_pgh.has_value()); - auto& pgh = **m_pgh.get(); - - REQUIRE(pgh.core_paragraph->supports.size() == 3); - REQUIRE(pgh.core_paragraph->supports[0] == "x64"); - REQUIRE(pgh.core_paragraph->supports[1] == "windows"); - REQUIRE(pgh.core_paragraph->supports[2] == "uwp"); -} - TEST_CASE ("SourceParagraph construct qualified depends", "[paragraph]") { auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Build-Depends", "libA (windows), libB (uwp)"}, - }}); + vcpkg::SourceControlFile::parse_control_file("", + std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "libA (windows), libB (uwp)"}, + }}); REQUIRE(m_pgh.has_value()); auto& pgh = **m_pgh.get(); @@ -127,11 +112,12 @@ TEST_CASE ("SourceParagraph construct qualified depends", "[paragraph]") TEST_CASE ("SourceParagraph default features", "[paragraph]") { auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "a"}, - {"Version", "1.0"}, - {"Default-Features", "a1"}, - }}); + vcpkg::SourceControlFile::parse_control_file("", + std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "a"}, + {"Version", "1.0"}, + {"Default-Features", "a1"}, + }}); REQUIRE(m_pgh.has_value()); auto& pgh = **m_pgh.get(); @@ -380,11 +366,12 @@ TEST_CASE ("BinaryParagraph serialize min", "[paragraph]") auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); REQUIRE(pghs.size() == 1); - REQUIRE(pghs[0].size() == 4); + REQUIRE(pghs[0].size() == 5); REQUIRE(pghs[0]["Package"] == "zlib"); REQUIRE(pghs[0]["Version"] == "1.2.8"); REQUIRE(pghs[0]["Architecture"] == "x86-windows"); REQUIRE(pghs[0]["Multi-Arch"] == "same"); + REQUIRE(pghs[0]["Type"] == "Port"); } TEST_CASE ("BinaryParagraph serialize max", "[paragraph]") @@ -402,13 +389,14 @@ TEST_CASE ("BinaryParagraph serialize max", "[paragraph]") auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); REQUIRE(pghs.size() == 1); - REQUIRE(pghs[0].size() == 7); + REQUIRE(pghs[0].size() == 8); REQUIRE(pghs[0]["Package"] == "zlib"); REQUIRE(pghs[0]["Version"] == "1.2.8"); REQUIRE(pghs[0]["Architecture"] == "x86-windows"); REQUIRE(pghs[0]["Multi-Arch"] == "same"); REQUIRE(pghs[0]["Description"] == "first line\n second line"); REQUIRE(pghs[0]["Depends"] == "dep"); + REQUIRE(pghs[0]["Type"] == "Port"); } TEST_CASE ("BinaryParagraph serialize multiple deps", "[paragraph]") diff --git a/toolsrc/src/vcpkg-test/plan.cpp b/toolsrc/src/vcpkg-test/plan.cpp index e354b7551..594c9783b 100644 --- a/toolsrc/src/vcpkg-test/plan.cpp +++ b/toolsrc/src/vcpkg-test/plan.cpp @@ -1,7 +1,10 @@ #include <catch2/catch.hpp> +#include <vcpkg-test/mockcmakevarprovider.h> #include <vcpkg-test/util.h> +#include <vcpkg/base/graphs.h> #include <vcpkg/dependencies.h> +#include <vcpkg/portfileprovider.h> #include <vcpkg/sourceparagraph.h> #include <vcpkg/triplet.h> @@ -11,54 +14,28 @@ using namespace vcpkg; +using Test::make_control_file; using Test::make_status_feature_pgh; using Test::make_status_pgh; +using Test::MockCMakeVarProvider; +using Test::PackageSpecMap; using Test::unsafe_pspec; -static std::unique_ptr<SourceControlFile> make_control_file( - const char* name, - const char* depends, - const std::vector<std::pair<const char*, const char*>>& features = {}, - const std::vector<const char*>& default_features = {}) -{ - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<Pgh> scf_pghs; - scf_pghs.push_back(Pgh{{"Source", name}, - {"Version", "0"}, - {"Build-Depends", depends}, - {"Default-Features", Strings::join(", ", default_features)}}); - for (auto&& feature : features) - { - 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)); - REQUIRE(m_pgh.has_value()); - return std::move(*m_pgh.get()); -} - /// <summary> /// Assert that the given action an install of given features from given package. /// </summary> -static void features_check(Dependencies::AnyAction& install_action, +static void features_check(Dependencies::InstallPlanAction& plan, std::string pkg_name, - std::vector<std::string> vec, + std::vector<std::string> expected_features, const Triplet& triplet = Triplet::X86_WINDOWS) { - REQUIRE(install_action.install_action.has_value()); - const auto& plan = install_action.install_action.value_or_exit(VCPKG_LINE_INFO); const auto& feature_list = plan.feature_list; REQUIRE(plan.spec.triplet().to_string() == triplet.to_string()); + REQUIRE(pkg_name == plan.spec.name()); + REQUIRE(feature_list.size() == expected_features.size()); - auto& scfl = *plan.source_control_file_location.get(); - REQUIRE(pkg_name == scfl.source_control_file->core_paragraph->name); - REQUIRE(feature_list.size() == vec.size()); - - for (auto&& feature_name : vec) + for (auto&& feature_name : expected_features) { // TODO: see if this can be simplified if (feature_name == "core" || feature_name == "") @@ -74,42 +51,14 @@ static void features_check(Dependencies::AnyAction& install_action, /// <summary> /// Assert that the given action is a remove of given package. /// </summary> -static void remove_plan_check(Dependencies::AnyAction& remove_action, +static void remove_plan_check(Dependencies::RemovePlanAction& plan, std::string pkg_name, const Triplet& triplet = Triplet::X86_WINDOWS) { - const auto& plan = remove_action.remove_action.value_or_exit(VCPKG_LINE_INFO); REQUIRE(plan.spec.triplet().to_string() == triplet.to_string()); REQUIRE(pkg_name == plan.spec.name()); } -/// <summary> -/// Map of source control files by their package name. -/// </summary> -struct PackageSpecMap -{ - std::unordered_map<std::string, SourceControlFileLocation> map; - Triplet triplet; - PackageSpecMap(const Triplet& t = Triplet::X86_WINDOWS) noexcept { triplet = t; } - - PackageSpec emplace(const char* name, - const char* depends = "", - const std::vector<std::pair<const char*, const char*>>& features = {}, - const std::vector<const char*>& default_features = {}) - { - auto scfl = SourceControlFileLocation{make_control_file(name, depends, features, default_features), ""}; - return emplace(std::move(scfl)); - } - - PackageSpec emplace(vcpkg::SourceControlFileLocation&& scfl) - { - auto spec = PackageSpec::from_name_and_triplet(scfl.source_control_file->core_paragraph->name, triplet); - REQUIRE(spec.has_value()); - map.emplace(scfl.source_control_file->core_paragraph->name, std::move(scfl)); - return PackageSpec{*spec.get()}; - } -}; - TEST_CASE ("basic install scheme", "[plan]") { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; @@ -119,14 +68,16 @@ TEST_CASE ("basic install scheme", "[plan]") auto spec_b = spec_map.emplace("b", "c"); auto spec_c = spec_map.emplace("c"); - Dependencies::MapPortFileProvider map_port(spec_map.map); + PortFileProvider::MapPortFileProvider map_port(spec_map.map); + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan( - map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); + map_port, var_provider, {FullPackageSpec{spec_a, {}}}, StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 3); - REQUIRE(install_plan.at(0).spec().name() == "c"); - REQUIRE(install_plan.at(1).spec().name() == "b"); - REQUIRE(install_plan.at(2).spec().name() == "a"); + REQUIRE(install_plan.install_actions.at(0).spec.name() == "c"); + REQUIRE(install_plan.install_actions.at(1).spec.name() == "b"); + REQUIRE(install_plan.install_actions.at(2).spec.name() == "a"); } TEST_CASE ("multiple install scheme", "[plan]") @@ -143,17 +94,21 @@ TEST_CASE ("multiple install scheme", "[plan]") auto spec_g = spec_map.emplace("g"); auto spec_h = spec_map.emplace("h"); - Dependencies::MapPortFileProvider map_port(spec_map.map); + PortFileProvider::MapPortFileProvider map_port(spec_map.map); + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan( map_port, - {FeatureSpec{spec_a, ""}, FeatureSpec{spec_b, ""}, FeatureSpec{spec_c, ""}}, + var_provider, + {FullPackageSpec{spec_a}, FullPackageSpec{spec_b}, FullPackageSpec{spec_c}}, StatusParagraphs(std::move(status_paragraphs))); auto iterator_pos = [&](const PackageSpec& spec) { - auto it = - std::find_if(install_plan.begin(), install_plan.end(), [&](auto& action) { return action.spec() == spec; }); - REQUIRE(it != install_plan.end()); - return it - install_plan.begin(); + auto it = std::find_if(install_plan.install_actions.begin(), + install_plan.install_actions.end(), + [&](auto& action) { return action.spec == spec; }); + REQUIRE(it != install_plan.install_actions.end()); + return it - install_plan.install_actions.begin(); }; const auto a_pos = iterator_pos(spec_a); @@ -184,12 +139,14 @@ TEST_CASE ("existing package scheme", "[plan]") PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a")}; + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + map_port, var_provider, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 1); - const auto p = install_plan.at(0).install_action.get(); - REQUIRE(p); + const auto p = &install_plan.already_installed.at(0); REQUIRE(p->spec.name() == "a"); REQUIRE(p->plan_type == Dependencies::InstallPlanType::ALREADY_INSTALLED); REQUIRE(p->request_type == Dependencies::RequestType::USER_REQUESTED); @@ -203,18 +160,19 @@ TEST_CASE ("user requested package scheme", "[plan]") const auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")}; const auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + const auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + map_port, var_provider, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 2); - const auto p = install_plan.at(0).install_action.get(); - REQUIRE(p); + const auto p = &install_plan.install_actions.at(0); REQUIRE(p->spec.name() == "b"); REQUIRE(p->plan_type == Dependencies::InstallPlanType::BUILD_AND_INSTALL); REQUIRE(p->request_type == Dependencies::RequestType::AUTO_SELECTED); - const auto p2 = install_plan.at(1).install_action.get(); - REQUIRE(p2); + const auto p2 = &install_plan.install_actions.at(1); REQUIRE(p2->spec.name() == "a"); REQUIRE(p2->plan_type == Dependencies::InstallPlanType::BUILD_AND_INSTALL); REQUIRE(p2->request_type == Dependencies::RequestType::USER_REQUESTED); @@ -239,19 +197,21 @@ TEST_CASE ("long install scheme", "[plan]") auto spec_j = spec_map.emplace("j", "k"); auto spec_k = spec_map.emplace("k"); - Dependencies::MapPortFileProvider map_port(spec_map.map); - auto install_plan = Dependencies::create_feature_install_plan( - map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_feature_install_plan( + map_port, var_provider, {FullPackageSpec{spec_a}}, StatusParagraphs(std::move(status_paragraphs))); + auto& install_plan = plan.install_actions; REQUIRE(install_plan.size() == 8); - REQUIRE(install_plan.at(0).spec().name() == "h"); - REQUIRE(install_plan.at(1).spec().name() == "g"); - REQUIRE(install_plan.at(2).spec().name() == "f"); - REQUIRE(install_plan.at(3).spec().name() == "e"); - REQUIRE(install_plan.at(4).spec().name() == "d"); - REQUIRE(install_plan.at(5).spec().name() == "c"); - REQUIRE(install_plan.at(6).spec().name() == "b"); - REQUIRE(install_plan.at(7).spec().name() == "a"); + REQUIRE(install_plan.at(0).spec.name() == "h"); + REQUIRE(install_plan.at(1).spec.name() == "g"); + REQUIRE(install_plan.at(2).spec.name() == "f"); + REQUIRE(install_plan.at(3).spec.name() == "e"); + REQUIRE(install_plan.at(4).spec.name() == "d"); + REQUIRE(install_plan.at(5).spec.name() == "c"); + REQUIRE(install_plan.at(6).spec.name() == "b"); + REQUIRE(install_plan.at(7).spec.name() == "a"); } TEST_CASE ("basic feature test 1", "[plan]") @@ -265,14 +225,17 @@ TEST_CASE ("basic feature test 1", "[plan]") 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, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; - REQUIRE(install_plan.size() == 4); - remove_plan_check(install_plan.at(0), "a"); - remove_plan_check(install_plan.at(1), "b"); - features_check(install_plan.at(2), "b", {"b1", "core", "b1"}); - features_check(install_plan.at(3), "a", {"a1", "core"}); + auto plan = Dependencies::create_feature_install_plan( + map_port, var_provider, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(plan.size() == 4); + remove_plan_check(plan.remove_actions.at(0), "a"); + remove_plan_check(plan.remove_actions.at(1), "b"); + features_check(plan.install_actions.at(0), "b", {"b1", "core", "b1"}); + features_check(plan.install_actions.at(1), "a", {"a1", "core"}); } TEST_CASE ("basic feature test 2", "[plan]") @@ -284,9 +247,13 @@ TEST_CASE ("basic feature test 2", "[plan]") 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, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto plan = Dependencies::create_feature_install_plan( + map_port, var_provider, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); + auto& install_plan = plan.install_actions; REQUIRE(install_plan.size() == 2); features_check(install_plan.at(0), "b", {"b1", "b2", "core"}); features_check(install_plan.at(1), "a", {"a1", "core"}); @@ -303,15 +270,18 @@ TEST_CASE ("basic feature test 3", "[plan]") 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, - FullPackageSpec::to_feature_specs({spec_c, spec_a}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto plan = Dependencies::create_feature_install_plan( + map_port, var_provider, {spec_c, spec_a}, StatusParagraphs(std::move(status_paragraphs))); - REQUIRE(install_plan.size() == 4); - remove_plan_check(install_plan.at(0), "a"); - features_check(install_plan.at(1), "b", {"core"}); - features_check(install_plan.at(2), "a", {"a1", "core"}); - features_check(install_plan.at(3), "c", {"core"}); + REQUIRE(plan.size() == 4); + remove_plan_check(plan.remove_actions.at(0), "a"); + auto& install_plan = plan.install_actions; + features_check(install_plan.at(0), "b", {"core"}); + features_check(install_plan.at(1), "a", {"a1", "core"}); + features_check(install_plan.at(2), "c", {"core"}); } TEST_CASE ("basic feature test 4", "[plan]") @@ -326,11 +296,14 @@ TEST_CASE ("basic feature test 4", "[plan]") auto spec_b = FullPackageSpec{spec_map.emplace("b")}; auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, FullPackageSpec::to_feature_specs({spec_c}), StatusParagraphs(std::move(status_paragraphs))); + map_port, var_provider, {spec_c}, StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "c", {"core"}); + features_check(install_plan.install_actions.at(0), "c", {"core"}); } TEST_CASE ("basic feature test 5", "[plan]") @@ -343,12 +316,15 @@ TEST_CASE ("basic feature test 5", "[plan]") 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", ""}})}; + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + map_port, var_provider, {spec_a}, StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 2); - features_check(install_plan.at(0), "b", {"core", "b2"}); - features_check(install_plan.at(1), "a", {"core", "a3", "a2"}); + features_check(install_plan.install_actions.at(0), "b", {"core", "b2"}); + features_check(install_plan.install_actions.at(1), "a", {"core", "a3", "a2"}); } TEST_CASE ("basic feature test 6", "[plan]") @@ -360,14 +336,16 @@ TEST_CASE ("basic feature test 6", "[plan]") 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, - FullPackageSpec::to_feature_specs({spec_a, spec_b}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; - REQUIRE(install_plan.size() == 3); - remove_plan_check(install_plan.at(0), "b"); - features_check(install_plan.at(1), "b", {"core", "b1"}); - features_check(install_plan.at(2), "a", {"core"}); + auto plan = Dependencies::create_feature_install_plan( + map_port, var_provider, {spec_a, spec_b}, StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(plan.size() == 3); + remove_plan_check(plan.remove_actions.at(0), "b"); + features_check(plan.install_actions.at(0), "b", {"core", "b1"}); + features_check(plan.install_actions.at(1), "a", {"core"}); } TEST_CASE ("basic feature test 7", "[plan]") @@ -382,20 +360,23 @@ TEST_CASE ("basic feature test 7", "[plan]") 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, FullPackageSpec::to_feature_specs({spec_b}), StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto plan = Dependencies::create_feature_install_plan( + map_port, var_provider, {spec_b}, StatusParagraphs(std::move(status_paragraphs))); - REQUIRE(install_plan.size() == 5); - remove_plan_check(install_plan.at(0), "x"); - remove_plan_check(install_plan.at(1), "b"); + REQUIRE(plan.size() == 5); + remove_plan_check(plan.remove_actions.at(0), "x"); + remove_plan_check(plan.remove_actions.at(1), "b"); // TODO: order here may change but A < X, and B anywhere - features_check(install_plan.at(2), "b", {"core", "b1"}); - features_check(install_plan.at(3), "a", {"core"}); - features_check(install_plan.at(4), "x", {"core"}); + features_check(plan.install_actions.at(0), "b", {"core", "b1"}); + features_check(plan.install_actions.at(1), "a", {"core"}); + features_check(plan.install_actions.at(2), "x", {"core"}); } -TEST_CASE ("basic feature test 8", "[plan][!mayfail]") +TEST_CASE ("basic feature test 8", "[plan]") { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; status_paragraphs.push_back(make_status_pgh("a")); @@ -413,19 +394,23 @@ TEST_CASE ("basic feature test 8", "[plan][!mayfail]") 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, - FullPackageSpec::to_feature_specs({spec_c_64, spec_a_86, spec_a_64, spec_c_86}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {spec_c_64, spec_a_86, spec_a_64, spec_c_86}, + StatusParagraphs(std::move(status_paragraphs))); - remove_plan_check(install_plan.at(0), "a", Triplet::X64_WINDOWS); - remove_plan_check(install_plan.at(1), "a"); - features_check(install_plan.at(2), "b", {"core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(3), "a", {"a1", "core"}, Triplet::X64_WINDOWS); + remove_plan_check(plan.remove_actions.at(0), "a", Triplet::X64_WINDOWS); + remove_plan_check(plan.remove_actions.at(1), "a"); + auto& install_plan = plan.install_actions; + features_check(install_plan.at(0), "b", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(1), "a", {"a1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(2), "b", {"core"}); + features_check(install_plan.at(3), "a", {"a1", "core"}); features_check(install_plan.at(4), "c", {"core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(5), "b", {"core"}); - features_check(install_plan.at(6), "a", {"a1", "core"}); - features_check(install_plan.at(7), "c", {"core"}); + features_check(install_plan.at(5), "c", {"core"}); } TEST_CASE ("install all features test", "[plan]") @@ -438,13 +423,17 @@ TEST_CASE ("install all features test", "[plan]") auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); REQUIRE(install_specs.has_value()); if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "a", {"0", "1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "a", {"0", "1", "core"}, Triplet::X64_WINDOWS); } TEST_CASE ("install default features test 1", "[plan]") @@ -457,14 +446,18 @@ TEST_CASE ("install default features test 1", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); // Expect the default feature "1" to be installed, but not "0" REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "a", {"1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "a", {"1", "core"}, Triplet::X64_WINDOWS); } TEST_CASE ("install default features test 2", "[plan]") @@ -482,16 +475,20 @@ TEST_CASE ("install default features test 2", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); // Expect "a" to get removed for rebuild and then installed with default // features. REQUIRE(install_plan.size() == 2); - remove_plan_check(install_plan.at(0), "a", Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "a", {"a1", "core"}, Triplet::X64_WINDOWS); + remove_plan_check(install_plan.remove_actions.at(0), "a", Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "a", {"a1", "core"}, Triplet::X64_WINDOWS); } TEST_CASE ("install default features test 3", "[plan]") @@ -504,14 +501,18 @@ TEST_CASE ("install default features test 3", "[plan]") // Explicitly install "a" without default features auto install_specs = FullPackageSpec::from_string("a[core]", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); // Expect the default feature not to get installed. REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "a", {"core"}, Triplet::X64_WINDOWS); } TEST_CASE ("install default features of dependency test 1", "[plan]") @@ -526,16 +527,76 @@ TEST_CASE ("install default features of dependency test 1", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed and defaults of "b" through the dependency, + // as no explicit features of "b" are installed by the user. + REQUIRE(install_plan.size() == 2); + features_check(install_plan.install_actions.at(0), "b", {"b1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(1), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("do not install default features of dependency test 1", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto spec_a = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto spec_b = FullPackageSpec::from_string("b[core]", Triplet::X64_WINDOWS); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + map_port, + var_provider, + {spec_a.value_or_exit(VCPKG_LINE_INFO), spec_b.value_or_exit(VCPKG_LINE_INFO)}, StatusParagraphs(std::move(status_paragraphs))); // Expect "a" to get installed and defaults of "b" through the dependency, // as no explicit features of "b" are installed by the user. REQUIRE(install_plan.size() == 2); - features_check(install_plan.at(0), "b", {"b1", "core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "a", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "b", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(1), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install default features of dependency test 2", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the default features of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto spec_a = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto spec_b = FullPackageSpec::from_string("b[core]", Triplet::X64_WINDOWS); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan( + map_port, + var_provider, + {spec_a.value_or_exit(VCPKG_LINE_INFO), spec_b.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed and defaults of "b" through the dependency + REQUIRE(install_plan.size() == 2); + features_check(install_plan.install_actions.at(0), "b", {"b1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(1), "a", {"core"}, Triplet::X64_WINDOWS); } TEST_CASE ("do not install default features of existing dependency", "[plan]") @@ -554,17 +615,20 @@ TEST_CASE ("do not install default features of existing dependency", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); // Expect "a" to get installed, but not require rebuilding "b" REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "a", {"core"}, Triplet::X64_WINDOWS); } -TEST_CASE ("install default features of dependency test 2", "[plan]") +TEST_CASE ("install default features of dependency test 3", "[plan]") { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; status_paragraphs.push_back(make_status_pgh("b")); @@ -580,15 +644,18 @@ TEST_CASE ("install default features of dependency test 2", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); // Expect "a" to get installed, not the defaults of "b", as the required // dependencies are already there, installed explicitly by the user. REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "a", {"core"}, Triplet::X64_WINDOWS); } TEST_CASE ("install plan action dependencies", "[plan]") @@ -604,19 +671,23 @@ TEST_CASE ("install plan action dependencies", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 3); - features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "c", {"core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS); - REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c}); + // TODO: Figure out what to do with these tests + features_check(install_plan.install_actions.at(1), "b", {"core"}, Triplet::X64_WINDOWS); + // REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c}); - features_check(install_plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS); - REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b}); + features_check(install_plan.install_actions.at(2), "a", {"core"}, Triplet::X64_WINDOWS); + // REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b}); } TEST_CASE ("install plan action dependencies 2", "[plan]") @@ -632,19 +703,23 @@ TEST_CASE ("install plan action dependencies 2", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 3); - features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "c", {"core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS); - REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c}); + features_check(install_plan.install_actions.at(1), "b", {"core"}, Triplet::X64_WINDOWS); + // REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c}); - features_check(install_plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS); - REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b, spec_c}); + features_check(install_plan.install_actions.at(2), "a", {"core"}, Triplet::X64_WINDOWS); + // REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b, + // spec_c}); } TEST_CASE ("install plan action dependencies 3", "[plan]") @@ -658,14 +733,17 @@ TEST_CASE ("install plan action dependencies 3", "[plan]") // Install "a" (without explicit feature specification) auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = Dependencies::create_feature_install_plan(map_port, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 1); - features_check(install_plan.at(0), "a", {"1", "0", "core"}, Triplet::X64_WINDOWS); - REQUIRE(install_plan.at(0).install_action.get()->computed_dependencies == std::vector<PackageSpec>{}); + features_check(install_plan.install_actions.at(0), "a", {"1", "0", "core"}, Triplet::X64_WINDOWS); + // REQUIRE(install_plan.at(0).install_action.get()->computed_dependencies == std::vector<PackageSpec>{}); } TEST_CASE ("install with default features", "[plan]") @@ -678,14 +756,20 @@ TEST_CASE ("install with default features", "[plan]") auto b_spec = spec_map.emplace("b", "", {{"0", ""}}, {"0"}); auto a_spec = spec_map.emplace("a", "b[core]", {{"0", ""}}); - // Install "a" and indicate that "b" should not install default features - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, {FeatureSpec{a_spec, "0"}, FeatureSpec{b_spec, "core"}}, status_db); + PortFileProvider::MapPortFileProvider map_port{spec_map.map}; + MockCMakeVarProvider var_provider; + + auto install_plan = + Dependencies::create_feature_install_plan(map_port, + var_provider, + {FullPackageSpec{a_spec, {"0"}}, FullPackageSpec{b_spec, {"core"}}}, + StatusParagraphs(std::move(status_db))); + // Install "a" and indicate that "b" should not install default features REQUIRE(install_plan.size() == 3); - remove_plan_check(install_plan.at(0), "a"); - features_check(install_plan.at(1), "b", {"core"}); - features_check(install_plan.at(2), "a", {"0", "core"}); + remove_plan_check(install_plan.remove_actions.at(0), "a"); + features_check(install_plan.install_actions.at(0), "b", {"core"}); + features_check(install_plan.install_actions.at(1), "a", {"0", "core"}); } TEST_CASE ("upgrade with default features 1", "[plan]") @@ -699,18 +783,15 @@ TEST_CASE ("upgrade with default features 1", "[plan]") PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); // The upgrade should not install the default feature REQUIRE(plan.size() == 2); - REQUIRE(plan.at(0).spec().name() == "a"); - remove_plan_check(plan.at(0), "a"); - features_check(plan.at(1), "a", {"core", "0"}); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "a", {"core", "0"}); } TEST_CASE ("upgrade with default features 2", "[plan]") @@ -726,19 +807,16 @@ TEST_CASE ("upgrade with default features 2", "[plan]") auto spec_a = spec_map.emplace("a", "b[core]"); auto spec_b = spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0", "b1"}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - graph.upgrade(spec_b); - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a, spec_b}, status_db); // The upgrade should install the new default feature b1 but not b0 REQUIRE(plan.size() == 4); - remove_plan_check(plan.at(0), "a", Triplet::X64_WINDOWS); - remove_plan_check(plan.at(1), "b", Triplet::X64_WINDOWS); - features_check(plan.at(2), "b", {"core", "b1"}, Triplet::X64_WINDOWS); - features_check(plan.at(3), "a", {"core"}, Triplet::X64_WINDOWS); + remove_plan_check(plan.remove_actions.at(0), "a", Triplet::X64_WINDOWS); + remove_plan_check(plan.remove_actions.at(1), "b", Triplet::X64_WINDOWS); + features_check(plan.install_actions.at(0), "b", {"core", "b1"}, Triplet::X64_WINDOWS); + features_check(plan.install_actions.at(1), "a", {"core"}, Triplet::X64_WINDOWS); } TEST_CASE ("upgrade with default features 3", "[plan]") @@ -754,17 +832,15 @@ TEST_CASE ("upgrade with default features 3", "[plan]") auto spec_a = spec_map.emplace("a", "b[core]"); spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); // The upgrade should install the default feature REQUIRE(plan.size() == 3); - remove_plan_check(plan.at(0), "a", Triplet::X64_WINDOWS); - features_check(plan.at(1), "b", {"b0", "core"}, Triplet::X64_WINDOWS); - features_check(plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS); + remove_plan_check(plan.remove_actions.at(0), "a", Triplet::X64_WINDOWS); + features_check(plan.install_actions.at(0), "b", {"b0", "core"}, Triplet::X64_WINDOWS); + features_check(plan.install_actions.at(1), "a", {"core"}, Triplet::X64_WINDOWS); } TEST_CASE ("upgrade with new default feature", "[plan]") @@ -777,16 +853,14 @@ TEST_CASE ("upgrade with new default feature", "[plan]") PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}, {"2", ""}}, {"0", "1"}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); // The upgrade should install the new default feature but not the old default feature 0 REQUIRE(plan.size() == 2); - remove_plan_check(plan.at(0), "a", Triplet::X86_WINDOWS); - features_check(plan.at(1), "a", {"core", "1"}, Triplet::X86_WINDOWS); + remove_plan_check(plan.remove_actions.at(0), "a", Triplet::X86_WINDOWS); + features_check(plan.install_actions.at(0), "a", {"core", "1"}, Triplet::X86_WINDOWS); } TEST_CASE ("transitive features test", "[plan]") @@ -801,15 +875,18 @@ TEST_CASE ("transitive features test", "[plan]") auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); REQUIRE(install_specs.has_value()); if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan(provider, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 3); - features_check(install_plan.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); } TEST_CASE ("no transitive features test", "[plan]") @@ -824,15 +901,17 @@ TEST_CASE ("no transitive features test", "[plan]") auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); REQUIRE(install_specs.has_value()); if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan(provider, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 3); - features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "c", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(1), "b", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); } TEST_CASE ("only transitive features test", "[plan]") @@ -847,15 +926,17 @@ TEST_CASE ("only transitive features test", "[plan]") auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); REQUIRE(install_specs.has_value()); if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto install_plan = Dependencies::create_feature_install_plan(provider, + var_provider, + {install_specs.value_or_exit(VCPKG_LINE_INFO)}, + StatusParagraphs(std::move(status_paragraphs))); REQUIRE(install_plan.size() == 3); - features_check(install_plan.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.install_actions.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); } TEST_CASE ("basic remove scheme", "[plan]") @@ -973,18 +1054,13 @@ TEST_CASE ("basic upgrade scheme", "[plan]") PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 2); - REQUIRE(plan.at(0).spec().name() == "a"); - REQUIRE(plan.at(0).remove_action.has_value()); - REQUIRE(plan.at(1).spec().name() == "a"); - REQUIRE(plan.at(1).install_action.has_value()); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "a", {"core"}); } TEST_CASE ("basic upgrade scheme with recurse", "[plan]") @@ -998,25 +1074,15 @@ TEST_CASE ("basic upgrade scheme with recurse", "[plan]") auto spec_a = spec_map.emplace("a"); spec_map.emplace("b", "a"); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 4); - REQUIRE(plan.at(0).spec().name() == "b"); - REQUIRE(plan.at(0).remove_action.has_value()); - - REQUIRE(plan.at(1).spec().name() == "a"); - REQUIRE(plan.at(1).remove_action.has_value()); - - REQUIRE(plan.at(2).spec().name() == "a"); - REQUIRE(plan.at(2).install_action.has_value()); - - REQUIRE(plan.at(3).spec().name() == "b"); - REQUIRE(plan.at(3).install_action.has_value()); + remove_plan_check(plan.remove_actions.at(0), "b"); + remove_plan_check(plan.remove_actions.at(1), "a"); + features_check(plan.install_actions.at(0), "a", {"core"}); + features_check(plan.install_actions.at(1), "b", {"core"}); } TEST_CASE ("basic upgrade scheme with bystander", "[plan]") @@ -1030,18 +1096,13 @@ TEST_CASE ("basic upgrade scheme with bystander", "[plan]") auto spec_a = spec_map.emplace("a"); spec_map.emplace("b", "a"); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 2); - REQUIRE(plan.at(0).spec().name() == "a"); - REQUIRE(plan.at(0).remove_action.has_value()); - REQUIRE(plan.at(1).spec().name() == "a"); - REQUIRE(plan.at(1).install_action.has_value()); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "a", {"core"}); } TEST_CASE ("basic upgrade scheme with new dep", "[plan]") @@ -1054,20 +1115,14 @@ TEST_CASE ("basic upgrade scheme with new dep", "[plan]") auto spec_a = spec_map.emplace("a", "b"); spec_map.emplace("b"); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 3); - REQUIRE(plan.at(0).spec().name() == "a"); - REQUIRE(plan.at(0).remove_action.has_value()); - REQUIRE(plan.at(1).spec().name() == "b"); - REQUIRE(plan.at(1).install_action.has_value()); - REQUIRE(plan.at(2).spec().name() == "a"); - REQUIRE(plan.at(2).install_action.has_value()); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "b", {"core"}); + features_check(plan.install_actions.at(1), "a", {"core"}); } TEST_CASE ("basic upgrade scheme with features", "[plan]") @@ -1080,19 +1135,13 @@ TEST_CASE ("basic upgrade scheme with features", "[plan]") PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 2); - - REQUIRE(plan.at(0).spec().name() == "a"); - REQUIRE(plan.at(0).remove_action.has_value()); - - features_check(plan.at(1), "a", {"core", "a1"}); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "a", {"core", "a1"}); } TEST_CASE ("basic upgrade scheme with new default feature", "[plan]") @@ -1106,19 +1155,13 @@ TEST_CASE ("basic upgrade scheme with new default feature", "[plan]") PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 2); - - REQUIRE(plan.at(0).spec().name() == "a"); - REQUIRE(plan.at(0).remove_action.has_value()); - - features_check(plan.at(1), "a", {"core", "a1"}); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "a", {"core", "a1"}); } TEST_CASE ("basic upgrade scheme with self features", "[plan]") @@ -1132,21 +1175,13 @@ TEST_CASE ("basic upgrade scheme with self features", "[plan]") PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}, {"a2", "a[a1]"}}); - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); + PortFileProvider::MapPortFileProvider provider(spec_map.map); + MockCMakeVarProvider var_provider; + auto plan = Dependencies::create_upgrade_plan(provider, var_provider, {spec_a}, status_db); REQUIRE(plan.size() == 2); - - REQUIRE(plan.at(0).spec().name() == "a"); - REQUIRE(plan.at(0).remove_action.has_value()); - - REQUIRE(plan.at(1).spec().name() == "a"); - REQUIRE(plan.at(1).install_action.has_value()); - REQUIRE(plan.at(1).install_action.get()->feature_list == std::set<std::string>{"core", "a1", "a2"}); + remove_plan_check(plan.remove_actions.at(0), "a"); + features_check(plan.install_actions.at(0), "a", {"a1", "a2", "core"}); } TEST_CASE ("basic export scheme", "[plan]") diff --git a/toolsrc/src/vcpkg-test/specifier.cpp b/toolsrc/src/vcpkg-test/specifier.cpp index 33df8ba83..a0a3725e8 100644 --- a/toolsrc/src/vcpkg-test/specifier.cpp +++ b/toolsrc/src/vcpkg-test/specifier.cpp @@ -1,5 +1,6 @@ #include <catch2/catch.hpp> +#include <vcpkg/base/system.print.h> #include <vcpkg/base/util.h> #include <vcpkg/packagespec.h> @@ -14,11 +15,13 @@ TEST_CASE ("specifier conversion", "[specifier]") 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"}}}); - + auto fspecs = FullPackageSpec{a_spec, {"0", "1"}}.to_feature_specs({}, {}); + auto fspecs2 = FullPackageSpec{b_spec, {"2", "3"}}.to_feature_specs({}, {}); + Util::Vectors::append(&fspecs, fspecs2); + Util::sort(fspecs); REQUIRE(fspecs.size() == SPEC_SIZE); - std::array<const char*, SPEC_SIZE> features = {"", "0", "1", "", "2", "3"}; + std::array<const char*, SPEC_SIZE> features = {"0", "1", "core", "2", "3", "core"}; std::array<PackageSpec*, SPEC_SIZE> specs = {&a_spec, &a_spec, &a_spec, &b_spec, &b_spec, &b_spec}; for (std::size_t i = 0; i < SPEC_SIZE; ++i) @@ -101,19 +104,20 @@ TEST_CASE ("specifier parsing", "[specifier]") auto zlib = vcpkg::FullPackageSpec::from_string("zlib[0,1]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); auto openssl = vcpkg::FullPackageSpec::from_string("openssl[*]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); - auto specs = FullPackageSpec::to_feature_specs({zlib, openssl}); + auto specs = zlib.to_feature_specs({}, {}); + auto specs2 = openssl.to_feature_specs({}, {}); + Util::Vectors::append(&specs, specs2); Util::sort(specs); + auto spectargets = FeatureSpec::from_strings_and_triplet( { "openssl", "zlib", - "openssl[*]", "zlib[0]", "zlib[1]", }, Triplet::X86_UWP); Util::sort(spectargets); - REQUIRE(specs.size() == spectargets.size()); REQUIRE(Util::all_equal(specs, spectargets)); } diff --git a/toolsrc/src/vcpkg-test/supports.cpp b/toolsrc/src/vcpkg-test/supports.cpp deleted file mode 100644 index f4d8dc65a..000000000 --- a/toolsrc/src/vcpkg-test/supports.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include <catch2/catch.hpp> - -#include <vcpkg/sourceparagraph.h> - -using namespace vcpkg; -using Parse::parse_comma_list; - -TEST_CASE ("parse supports all", "[supports]") -{ - auto v = Supports::parse({ - "x64", - "x86", - "arm", - "windows", - "uwp", - "v140", - "v141", - "crt-static", - "crt-dynamic", - }); - - REQUIRE(v.has_value()); - - REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::UWP, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - REQUIRE(v.get()->is_supported(System::CPUArchitecture::ARM, - Supports::Platform::WINDOWS, - Supports::Linkage::STATIC, - Supports::ToolsetVersion::V141)); -} - -TEST_CASE ("parse supports invalid", "[supports]") -{ - auto v = Supports::parse({"arm64"}); - - REQUIRE_FALSE(v.has_value()); - - REQUIRE(v.error().size() == 1); - REQUIRE(v.error().at(0) == "arm64"); -} - -TEST_CASE ("parse supports case sensitive", "[supports]") -{ - auto v = Supports::parse({"Windows"}); - - REQUIRE_FALSE(v.has_value()); - REQUIRE(v.error().size() == 1); - REQUIRE(v.error().at(0) == "Windows"); -} - -TEST_CASE ("parse supports some", "[supports]") -{ - auto v = Supports::parse({ - "x64", - "x86", - "windows", - }); - - REQUIRE(v.has_value()); - - REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::WINDOWS, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - REQUIRE_FALSE(v.get()->is_supported(System::CPUArchitecture::ARM, - Supports::Platform::WINDOWS, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - REQUIRE_FALSE(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::UWP, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::WINDOWS, - Supports::Linkage::STATIC, - Supports::ToolsetVersion::V141)); -} diff --git a/toolsrc/src/vcpkg-test/update.cpp b/toolsrc/src/vcpkg-test/update.cpp index 6f1a87d23..a362df720 100644 --- a/toolsrc/src/vcpkg-test/update.cpp +++ b/toolsrc/src/vcpkg-test/update.cpp @@ -20,9 +20,9 @@ TEST_CASE ("find outdated packages basic", "[update]") StatusParagraphs status_db(std::move(status_paragraphs)); std::unordered_map<std::string, SourceControlFileLocation> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + auto scf = unwrap(SourceControlFile::parse_control_file("", Pgh{{{"Source", "a"}, {"Version", "0"}}})); map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); - Dependencies::MapPortFileProvider provider(map); + PortFileProvider::MapPortFileProvider provider(map); auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); @@ -44,9 +44,9 @@ TEST_CASE ("find outdated packages features", "[update]") StatusParagraphs status_db(std::move(status_paragraphs)); std::unordered_map<std::string, SourceControlFileLocation> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + auto scf = unwrap(SourceControlFile::parse_control_file("", Pgh{{{"Source", "a"}, {"Version", "0"}}})); map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); - Dependencies::MapPortFileProvider provider(map); + PortFileProvider::MapPortFileProvider provider(map); auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); @@ -70,9 +70,9 @@ TEST_CASE ("find outdated packages features 2", "[update]") StatusParagraphs status_db(std::move(status_paragraphs)); std::unordered_map<std::string, SourceControlFileLocation> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + auto scf = unwrap(SourceControlFile::parse_control_file("", Pgh{{{"Source", "a"}, {"Version", "0"}}})); map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); - Dependencies::MapPortFileProvider provider(map); + PortFileProvider::MapPortFileProvider provider(map); auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); @@ -91,9 +91,9 @@ TEST_CASE ("find outdated packages none", "[update]") StatusParagraphs status_db(std::move(status_paragraphs)); std::unordered_map<std::string, SourceControlFileLocation> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "2"}}})); + auto scf = unwrap(SourceControlFile::parse_control_file("", Pgh{{{"Source", "a"}, {"Version", "2"}}})); map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); - Dependencies::MapPortFileProvider provider(map); + PortFileProvider::MapPortFileProvider provider(map); auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); diff --git a/toolsrc/src/vcpkg-test/util.cpp b/toolsrc/src/vcpkg-test/util.cpp index daa21567d..db310f7a0 100644 --- a/toolsrc/src/vcpkg-test/util.cpp +++ b/toolsrc/src/vcpkg-test/util.cpp @@ -38,6 +38,31 @@ namespace vcpkg::Test { + std::unique_ptr<SourceControlFile> make_control_file( + const char* name, + const char* depends, + const std::vector<std::pair<const char*, const char*>>& features, + const std::vector<const char*>& default_features) + { + using Pgh = std::unordered_map<std::string, std::string>; + std::vector<Pgh> scf_pghs; + scf_pghs.push_back(Pgh{{"Source", name}, + {"Version", "0"}, + {"Build-Depends", depends}, + {"Default-Features", Strings::join(", ", default_features)}}); + for (auto&& feature : features) + { + 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)); + REQUIRE(m_pgh.has_value()); + return std::move(*m_pgh.get()); + } + std::unique_ptr<vcpkg::StatusParagraph> make_status_pgh(const char* name, const char* depends, const char* default_features, @@ -68,6 +93,23 @@ namespace vcpkg::Test {"Status", "install ok installed"}}); } + PackageSpec PackageSpecMap::emplace(const char* name, + const char* depends, + const std::vector<std::pair<const char*, const char*>>& features, + const std::vector<const char*>& default_features) + { + auto scfl = SourceControlFileLocation{make_control_file(name, depends, features, default_features), ""}; + return emplace(std::move(scfl)); + } + + PackageSpec PackageSpecMap::emplace(vcpkg::SourceControlFileLocation&& scfl) + { + auto spec = PackageSpec::from_name_and_triplet(scfl.source_control_file->core_paragraph->name, triplet); + REQUIRE(spec.has_value()); + map.emplace(scfl.source_control_file->core_paragraph->name, std::move(scfl)); + return PackageSpec{*spec.get()}; + } + PackageSpec unsafe_pspec(std::string name, Triplet t) { auto m_ret = PackageSpec::from_name_and_triplet(name, t); diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index c336d2f63..1f212dd94 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -262,31 +262,19 @@ static void load_config() } #if defined(_WIN32) -static std::string trim_path_from_command_line(const std::string& full_command_line) -{ - Checks::check_exit( - VCPKG_LINE_INFO, !full_command_line.empty(), "Internal failure - cannot have empty command line"); - - if (full_command_line[0] == '"') - { - auto it = std::find(full_command_line.cbegin() + 1, full_command_line.cend(), '"'); - if (it != full_command_line.cend()) // Skip over the quote - ++it; - while (it != full_command_line.cend() && *it == ' ') // Skip over a space - ++it; - return std::string(it, full_command_line.cend()); - } +// note: this prevents a false positive for -Wmissing-prototypes on clang-cl +int wmain(int, const wchar_t* const* const); - auto it = std::find(full_command_line.cbegin(), full_command_line.cend(), ' '); - while (it != full_command_line.cend() && *it == ' ') - ++it; - return std::string(it, full_command_line.cend()); +#if !defined(_MSC_VER) +#include <shellapi.h> +int main(int argc, const char* const* const /*argv*/) +{ + wchar_t **wargv; + wargv = CommandLineToArgvW(GetCommandLineW(), &argc); + return wmain(argc, wargv); } #endif -#if defined(_WIN32) -// note: this prevents a false positive for -Wmissing-prototypes on clang-cl -int wmain(int, const wchar_t* const*); int wmain(const int argc, const wchar_t* const* const argv) #else int main(const int argc, const char* const* const argv) diff --git a/toolsrc/src/vcpkg/base/downloads.cpp b/toolsrc/src/vcpkg/base/downloads.cpp index df6b1be09..995e2d4f3 100644 --- a/toolsrc/src/vcpkg/base/downloads.cpp +++ b/toolsrc/src/vcpkg/base/downloads.cpp @@ -2,13 +2,12 @@ #include <vcpkg/base/downloads.h> #include <vcpkg/base/hash.h> +#include <vcpkg/base/system.h> #include <vcpkg/base/system.process.h> #include <vcpkg/base/util.h> #if defined(_WIN32) #include <VersionHelpers.h> -#else -#include <vcpkg/base/system.h> #endif namespace vcpkg::Downloads @@ -43,8 +42,22 @@ namespace vcpkg::Downloads 0); Checks::check_exit(VCPKG_LINE_INFO, hSession, "WinHttpOpen() failed: %d", GetLastError()); + // If the environment variable HTTPS_PROXY is set + // use that variable as proxy. This situation might exist when user is in a company network + // with restricted network/proxy settings + auto maybe_https_proxy_env = System::get_environment_variable("HTTPS_PROXY"); + if (auto p_https_proxy = maybe_https_proxy_env.get()) + { + std::wstring env_proxy_settings = Strings::to_utf16(*p_https_proxy); + WINHTTP_PROXY_INFO proxy; + proxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy.lpszProxy = env_proxy_settings.data(); + proxy.lpszProxyBypass = nullptr; + + WinHttpSetOption(hSession, WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy)); + } // Win7 IE Proxy fallback - if (IsWindows7OrGreater() && !IsWindows8Point1OrGreater()) + else if (IsWindows7OrGreater() && !IsWindows8Point1OrGreater()) { // First check if any proxy has been found automatically WINHTTP_PROXY_INFO proxyInfo; @@ -63,6 +76,9 @@ namespace vcpkg::Downloads proxy.lpszProxy = ieProxy.lpszProxy; proxy.lpszProxyBypass = ieProxy.lpszProxyBypass; WinHttpSetOption(hSession, WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy)); + GlobalFree(ieProxy.lpszProxy); + GlobalFree(ieProxy.lpszProxyBypass); + GlobalFree(ieProxy.lpszAutoConfigUrl); } } } @@ -89,6 +105,7 @@ namespace vcpkg::Downloads // Send a request. auto bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpSendRequest() failed: %d", GetLastError()); // End the request. diff --git a/toolsrc/src/vcpkg/base/hash.cpp b/toolsrc/src/vcpkg/base/hash.cpp index 11df3e329..81635b875 100644 --- a/toolsrc/src/vcpkg/base/hash.cpp +++ b/toolsrc/src/vcpkg/base/hash.cpp @@ -123,9 +123,9 @@ namespace vcpkg::Hash void top_bits(T) = delete; static constexpr uchar top_bits(uchar x) noexcept { return x; } - static constexpr uchar top_bits(std::uint32_t x) noexcept { return (x >> 24) & 0xFF; } - static constexpr uchar top_bits(std::uint64_t x) noexcept { return (x >> 56) & 0xFF; } - static constexpr uchar top_bits(UInt128 x) noexcept { return top_bits(x.top); } + [[maybe_unused]] static constexpr uchar top_bits(std::uint32_t x) noexcept { return (x >> 24) & 0xFF; } + [[maybe_unused]] static constexpr uchar top_bits(std::uint64_t x) noexcept { return (x >> 56) & 0xFF; } + [[maybe_unused]] static constexpr uchar top_bits(UInt128 x) noexcept { return top_bits(x.top); } // treats UIntTy as big endian for the purpose of this mapping template<class UIntTy> @@ -145,7 +145,7 @@ namespace vcpkg::Hash for (uchar& ch : buff) { ch = top_bits(tmp); - tmp <<= 8; + tmp = UIntTy(tmp << 8); } for (const auto byte : buff) diff --git a/toolsrc/src/vcpkg/base/machinetype.cpp b/toolsrc/src/vcpkg/base/machinetype.cpp index 2b7bd5e3a..237e1eb8b 100644 --- a/toolsrc/src/vcpkg/base/machinetype.cpp +++ b/toolsrc/src/vcpkg/base/machinetype.cpp @@ -35,7 +35,7 @@ namespace vcpkg case MachineType::SH5: case MachineType::THUMB: case MachineType::WCEMIPSV2: return t; - default: Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown machine type code 0x%x", value); + default: Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown machine type code 0x%hx", value); } } } diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp index c84b7f8be..b71ab7c44 100644 --- a/toolsrc/src/vcpkg/base/system.cpp +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -378,7 +378,7 @@ namespace vcpkg g_ctrl_c_state.transition_to_spawn_process(); auto clean_env = compute_clean_environment(extra_env, prepend_to_path); - windows_create_process(cmd_line, clean_env.data(), process_info, NULL); + windows_create_process(cmd_line, clean_env.data(), process_info, 0); CloseHandle(process_info.hThread); @@ -444,7 +444,7 @@ namespace vcpkg Debug::print("_wpopen(", actual_cmd_line, ")\n"); std::wstring output; - wchar_t buf[1024]; + auto buf = std::make_unique<wchar_t[]>(1024 * 32); g_ctrl_c_state.transition_to_spawn_process(); // Flush stdout before launching external process fflush(stdout); @@ -454,9 +454,9 @@ namespace vcpkg g_ctrl_c_state.transition_from_spawn_process(); return {1, Strings::to_utf8(output.c_str())}; } - while (fgetws(buf, 1024, pipe)) + while (fgetws(buf.get(), 1024 * 32, pipe)) { - output.append(buf); + output.append(buf.get()); } if (!feof(pipe)) { @@ -542,7 +542,7 @@ namespace vcpkg { HKEY k = nullptr; const LSTATUS ec = - RegOpenKeyExW(reinterpret_cast<HKEY>(base_hkey), Strings::to_utf16(sub_key).c_str(), NULL, KEY_READ, &k); + RegOpenKeyExW(reinterpret_cast<HKEY>(base_hkey), Strings::to_utf16(sub_key).c_str(), 0, KEY_READ, &k); if (ec != ERROR_SUCCESS) return nullopt; auto w_valuename = Strings::to_utf16(valuename); diff --git a/toolsrc/src/vcpkg/binaryparagraph.cpp b/toolsrc/src/vcpkg/binaryparagraph.cpp index 8b1886098..3404a6a48 100644 --- a/toolsrc/src/vcpkg/binaryparagraph.cpp +++ b/toolsrc/src/vcpkg/binaryparagraph.cpp @@ -2,6 +2,8 @@ #include <vcpkg/base/checks.h> #include <vcpkg/base/system.print.h> +#include <vcpkg/base/util.h> + #include <vcpkg/binaryparagraph.h> #include <vcpkg/parse.h> @@ -23,6 +25,7 @@ namespace vcpkg static const std::string MAINTAINER = "Maintainer"; static const std::string DEPENDS = "Depends"; static const std::string DEFAULTFEATURES = "Default-Features"; + static const std::string TYPE = "Type"; } BinaryParagraph::BinaryParagraph() = default; @@ -60,6 +63,8 @@ namespace vcpkg this->default_features = parse_comma_list(parser.optional_field(Fields::DEFAULTFEATURES)); } + this->type = Type::from_string(parser.optional_field(Fields::TYPE)); + if (const auto err = parser.error_info(this->spec.to_string())) { System::print2(System::Color::error, "Error: while parsing the Binary Paragraph for ", this->spec, '\n'); @@ -71,18 +76,30 @@ namespace vcpkg Checks::check_exit(VCPKG_LINE_INFO, multi_arch == "same", "Multi-Arch must be 'same' but was %s", multi_arch); } - BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const Triplet& triplet, const std::string& abi_tag) - : version(spgh.version), description(spgh.description), maintainer(spgh.maintainer), abi(abi_tag) + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, + const Triplet& triplet, + const std::string& abi_tag, + const std::vector<FeatureSpec>& deps) + : version(spgh.version) + , description(spgh.description) + , maintainer(spgh.maintainer) + , abi(abi_tag) + , type(spgh.type) { this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); - this->depends = filter_dependencies(spgh.depends, triplet); + this->depends = Util::fmap(deps, [](const FeatureSpec& spec) { return spec.to_string(); }); + Util::sort_unique_erase(this->depends); } - BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const FeatureParagraph& fpgh, const Triplet& triplet) - : version(), description(fpgh.description), maintainer(), feature(fpgh.name) + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, + const FeatureParagraph& fpgh, + const Triplet& triplet, + const std ::vector<FeatureSpec>& deps) + : version(), description(fpgh.description), maintainer(), feature(fpgh.name), type(spgh.type) { this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); - this->depends = filter_dependencies(fpgh.depends, triplet); + this->depends = Util::fmap(deps, [](const FeatureSpec& spec) { return spec.to_string(); }); + Util::sort_unique_erase(this->depends); } std::string BinaryParagraph::displayname() const @@ -119,5 +136,7 @@ namespace vcpkg if (!pgh.maintainer.empty()) out_str.append("Maintainer: ").append(pgh.maintainer).push_back('\n'); if (!pgh.abi.empty()) out_str.append("Abi: ").append(pgh.abi).push_back('\n'); if (!pgh.description.empty()) out_str.append("Description: ").append(pgh.description).push_back('\n'); + + out_str.append("Type: ").append(Type::to_string(pgh.type)).push_back('\n'); } } diff --git a/toolsrc/src/vcpkg/build.cpp b/toolsrc/src/vcpkg/build.cpp index f13dd2a25..bcacb331a 100644 --- a/toolsrc/src/vcpkg/build.cpp +++ b/toolsrc/src/vcpkg/build.cpp @@ -23,10 +23,11 @@ #include <vcpkg/statusparagraphs.h>
#include <vcpkg/vcpkglib.h>
+using namespace vcpkg;
using vcpkg::Build::BuildResult;
-using vcpkg::Dependencies::PathsPortFileProvider;
using vcpkg::Parse::ParseControlErrorInfo;
using vcpkg::Parse::ParseExpected;
+using vcpkg::PortFileProvider::PathsPortFileProvider;
namespace vcpkg::Build::Command
{
@@ -35,12 +36,21 @@ namespace vcpkg::Build::Command void perform_and_exit_ex(const FullPackageSpec& full_spec,
const SourceControlFileLocation& scfl,
+ const PathsPortFileProvider& provider,
const ParsedArguments& options,
const VcpkgPaths& paths)
{
vcpkg::Util::unused(options);
- const StatusParagraphs status_db = database_load_check(paths);
+ CMakeVars::TripletCMakeVarProvider var_provider(paths);
+ var_provider.load_dep_info_vars(std::array<PackageSpec, 1>{full_spec.package_spec});
+ var_provider.load_tag_vars(std::array<FullPackageSpec, 1>{full_spec}, provider);
+
+ StatusParagraphs status_db = database_load_check(paths);
+
+ auto action_plan = Dependencies::create_feature_install_plan(
+ provider, var_provider, std::vector<FullPackageSpec>{full_spec}, status_db);
+
const PackageSpec& spec = full_spec.package_spec;
const SourceControlFile& scf = *scfl.source_control_file;
@@ -62,10 +72,39 @@ namespace vcpkg::Build::Command Build::FailOnTombstone::NO,
};
- std::set<std::string> features_as_set(full_spec.features.begin(), full_spec.features.end());
- features_as_set.emplace("core");
+ std::unordered_map<std::string, std::vector<FeatureSpec>>* feature_dependencies = nullptr;
+ std::vector<PackageSpec>* package_dependencies = nullptr;
+ std::vector<std::string>* feature_list = nullptr;
+ for (auto& install_action : action_plan.already_installed)
+ {
+ if (install_action.spec == full_spec.package_spec)
+ {
+ feature_dependencies = &install_action.feature_dependencies;
+ package_dependencies = &install_action.package_dependencies;
+ feature_list = &install_action.feature_list;
+ }
+ }
+ for (auto& install_action : action_plan.install_actions)
+ {
+ if (install_action.spec == full_spec.package_spec)
+ {
+ feature_dependencies = &install_action.feature_dependencies;
+ package_dependencies = &install_action.package_dependencies;
+ feature_list = &install_action.feature_list;
+ }
+ }
+
+ Checks::check_exit(VCPKG_LINE_INFO, feature_dependencies != nullptr);
+ Checks::check_exit(VCPKG_LINE_INFO, package_dependencies != nullptr);
+ Checks::check_exit(VCPKG_LINE_INFO, feature_list != nullptr);
- const Build::BuildPackageConfig build_config{scfl, spec.triplet(), build_package_options, features_as_set};
+ const Build::BuildPackageConfig build_config{scfl,
+ spec.triplet(),
+ build_package_options,
+ var_provider,
+ *feature_dependencies,
+ *package_dependencies,
+ *feature_list};
const auto build_timer = Chrono::ElapsedTimer::create_started();
const auto result = Build::build_package(paths, build_config, status_db);
@@ -121,7 +160,7 @@ namespace vcpkg::Build::Command Checks::check_exit(VCPKG_LINE_INFO, scfl != nullptr, "Error: Couldn't find port '%s'", port_name);
- perform_and_exit_ex(spec, *scfl, options, paths);
+ perform_and_exit_ex(spec, *scfl, provider, options, paths);
}
}
@@ -133,6 +172,7 @@ namespace vcpkg::Build static const std::string NAME_ONLY_RELEASE_CRT = "PolicyOnlyReleaseCRT";
static const std::string NAME_EMPTY_INCLUDE_FOLDER = "PolicyEmptyIncludeFolder";
static const std::string NAME_ALLOW_OBSOLETE_MSVCRT = "PolicyAllowObsoleteMsvcrt";
+ static const std::string NAME_ALLOW_RESTRICTED_HEADERS = "PolicyAllowRestrictedHeaders";
const std::string& to_string(BuildPolicy policy)
{
@@ -144,6 +184,7 @@ namespace vcpkg::Build case BuildPolicy::ONLY_RELEASE_CRT: return NAME_ONLY_RELEASE_CRT;
case BuildPolicy::EMPTY_INCLUDE_FOLDER: return NAME_EMPTY_INCLUDE_FOLDER;
case BuildPolicy::ALLOW_OBSOLETE_MSVCRT: return NAME_ALLOW_OBSOLETE_MSVCRT;
+ case BuildPolicy::ALLOW_RESTRICTED_HEADERS: return NAME_ALLOW_RESTRICTED_HEADERS;
default: Checks::unreachable(VCPKG_LINE_INFO);
}
}
@@ -158,6 +199,7 @@ namespace vcpkg::Build case BuildPolicy::ONLY_RELEASE_CRT: return "VCPKG_POLICY_ONLY_RELEASE_CRT";
case BuildPolicy::EMPTY_INCLUDE_FOLDER: return "VCPKG_POLICY_EMPTY_INCLUDE_FOLDER";
case BuildPolicy::ALLOW_OBSOLETE_MSVCRT: return "VCPKG_POLICY_ALLOW_OBSOLETE_MSVCRT";
+ case BuildPolicy::ALLOW_RESTRICTED_HEADERS: return "VCPKG_POLICY_ALLOW_RESTRICTED_HEADERS";
default: Checks::unreachable(VCPKG_LINE_INFO);
}
}
@@ -260,20 +302,15 @@ namespace vcpkg::Build tonull);
}
- static BinaryParagraph create_binary_feature_control_file(const SourceParagraph& source_paragraph,
- const FeatureParagraph& feature_paragraph,
- const Triplet& triplet)
- {
- return BinaryParagraph(source_paragraph, feature_paragraph, triplet);
- }
-
- static std::unique_ptr<BinaryControlFile> create_binary_control_file(const SourceParagraph& source_paragraph,
- const Triplet& triplet,
- const BuildInfo& build_info,
- const std::string& abi_tag)
+ static std::unique_ptr<BinaryControlFile> create_binary_control_file(
+ const SourceParagraph& source_paragraph,
+ const Triplet& triplet,
+ const BuildInfo& build_info,
+ const std::string& abi_tag,
+ const std::vector<FeatureSpec>& core_dependencies)
{
auto bcf = std::make_unique<BinaryControlFile>();
- BinaryParagraph bpgh(source_paragraph, triplet, abi_tag);
+ BinaryParagraph bpgh(source_paragraph, triplet, abi_tag, core_dependencies);
if (const auto p_ver = build_info.version.get())
{
bpgh.version = *p_ver;
@@ -294,71 +331,6 @@ namespace vcpkg::Build paths.get_filesystem().write_contents(binary_control_file, start, VCPKG_LINE_INFO);
}
- static std::vector<Features> get_dependencies(const SourceControlFile& scf,
- const std::set<std::string>& feature_list,
- const Triplet& triplet)
- {
- return Util::fmap_flatten(feature_list, [&](std::string const& feature) -> std::vector<Features> {
- if (feature == "core")
- {
- return filter_dependencies_to_features(scf.core_paragraph->depends, triplet);
- }
-
- auto maybe_feature = scf.find_feature(feature);
- Checks::check_exit(VCPKG_LINE_INFO, maybe_feature.has_value());
-
- return filter_dependencies_to_features(maybe_feature.get()->depends, triplet);
- });
- }
-
- static std::vector<std::string> get_dependency_names(const SourceControlFile& scf,
- const std::set<std::string>& feature_list,
- const Triplet& triplet)
- {
- return Util::sort_unique_erase(
- Util::fmap(get_dependencies(scf, feature_list, triplet), [&](const Features& feat) { return feat.name; }));
- }
-
- static std::vector<FeatureSpec> compute_required_feature_specs(const BuildPackageConfig& config,
- const StatusParagraphs& status_db)
- {
- const Triplet& triplet = config.triplet;
-
- const std::vector<std::string> dep_strings = get_dependency_names(config.scf, config.feature_list, triplet);
-
- auto dep_fspecs = FeatureSpec::from_strings_and_triplet(dep_strings, triplet);
- Util::sort_unique_erase(dep_fspecs);
-
- // expand defaults
- std::vector<FeatureSpec> ret;
- for (auto&& fspec : dep_fspecs)
- {
- if (fspec.feature().empty())
- {
- // reference to default features
- const auto it = status_db.find_installed(fspec.spec());
- if (it == status_db.end())
- {
- // not currently installed, so just leave the default reference so it will fail later
- ret.push_back(fspec);
- }
- else
- {
- ret.emplace_back(fspec.spec(), "core");
- for (auto&& default_feature : it->get()->package.default_features)
- ret.emplace_back(fspec.spec(), default_feature);
- }
- }
- else
- {
- ret.push_back(fspec);
- }
- }
- Util::sort_unique_erase(ret);
-
- return ret;
- }
-
static int get_concurrency()
{
static int concurrency = [] {
@@ -423,30 +395,22 @@ namespace vcpkg::Build }
const Files::Filesystem& fs = paths.get_filesystem();
- if (fs.is_regular_file(config.port_dir / "environment-overrides.cmake"))
- {
- variables.emplace_back("VCPKG_ENV_OVERRIDES_FILE", config.port_dir / "environment-overrides.cmake");
- }
-
- std::vector<FeatureSpec> dependencies =
- filter_dependencies_to_specs(config.scfl.source_control_file->core_paragraph->depends, triplet);
- std::vector<std::string> port_toolchains;
- for (const FeatureSpec& dependency : dependencies)
+ std::vector<std::string> port_configs;
+ for (const PackageSpec& dependency : config.package_dependencies)
{
- const fs::path port_toolchain_path = paths.installed / dependency.triplet().canonical_name() / "share" /
- dependency.spec().name() / "port-toolchain.cmake";
+ const fs::path port_config_path = paths.installed / dependency.triplet().canonical_name() / "share" /
+ dependency.name() / "vcpkg-port-config.cmake";
- if (fs.is_regular_file(port_toolchain_path))
+ if (fs.is_regular_file(port_config_path))
{
- System::print2(port_toolchain_path.u8string());
- port_toolchains.emplace_back(port_toolchain_path.u8string());
+ port_configs.emplace_back(port_config_path.u8string());
}
}
- if (!port_toolchains.empty())
+ if (!port_configs.empty())
{
- variables.emplace_back("VCPKG_PORT_TOOLCHAINS", Strings::join(";", port_toolchains));
+ variables.emplace_back("VCPKG_PORT_CONFIGS", Strings::join(";", port_configs));
}
return variables;
@@ -497,6 +461,8 @@ namespace vcpkg::Build else
{
const auto algo = Hash::Algorithm::Sha1;
+ // TODO: Use file path as part of hash.
+ // REASON: Copying a triplet file without modifying it produces the same hash as the original.
hash = Hash::get_file_hash(VCPKG_LINE_INFO, fs, triplet_file_path, algo);
if (auto p = pre_build_info.external_toolchain_file.get())
@@ -550,10 +516,18 @@ namespace vcpkg::Build const Triplet& triplet = spec.triplet();
const auto& triplet_file_path = paths.get_triplet_file_path(spec.triplet()).u8string();
- if (!Strings::case_insensitive_ascii_starts_with(triplet_file_path, paths.triplets.u8string()))
+ if (Strings::case_insensitive_ascii_starts_with(triplet_file_path, paths.community_triplets.u8string()))
{
- System::printf("-- Loading triplet configuration from: %s\n", triplet_file_path);
+ System::printf(vcpkg::System::Color::warning,
+ "-- Using community triplet %s. This triplet configuration is not guaranteed to succeed.\n",
+ triplet.canonical_name());
+ System::printf("-- [COMMUNITY] Loading triplet configuration from: %s\n", triplet_file_path);
}
+ else if (!Strings::case_insensitive_ascii_starts_with(triplet_file_path, paths.triplets.u8string()))
+ {
+ System::printf("-- [OVERLAY] Loading triplet configuration from: %s\n", triplet_file_path);
+ }
+
if (!Strings::case_insensitive_ascii_starts_with(config.port_dir.u8string(), paths.ports.u8string()))
{
System::printf("-- Installing port from location: %s\n", config.port_dir.u8string());
@@ -605,8 +579,11 @@ namespace vcpkg::Build const size_t error_count =
PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info, config.port_dir);
- std::unique_ptr<BinaryControlFile> bcf =
- create_binary_control_file(*config.scf.core_paragraph, triplet, build_info, abi_tag);
+ auto find_itr = config.feature_dependencies.find("core");
+ Checks::check_exit(VCPKG_LINE_INFO, find_itr != config.feature_dependencies.end());
+
+ std::unique_ptr<BinaryControlFile> bcf = create_binary_control_file(
+ *config.scf.core_paragraph, triplet, build_info, abi_tag, std::move(find_itr->second));
if (error_count != 0)
{
@@ -617,8 +594,13 @@ namespace vcpkg::Build for (auto&& f_pgh : config.scf.feature_paragraphs)
{
if (f_pgh->name == feature)
- bcf->features.push_back(
- create_binary_feature_control_file(*config.scf.core_paragraph, *f_pgh, triplet));
+ {
+ find_itr = config.feature_dependencies.find(feature);
+ Checks::check_exit(VCPKG_LINE_INFO, find_itr != config.feature_dependencies.end());
+
+ bcf->features.emplace_back(
+ *config.scf.core_paragraph, *f_pgh, triplet, std::move(find_itr->second));
+ }
}
}
@@ -713,8 +695,11 @@ namespace vcpkg::Build paths.scripts / "cmake" / "vcpkg_fixup_cmake_targets.cmake",
Hash::Algorithm::Sha1));
+ abi_tag_entries.emplace_back("post_build_checks", "2");
abi_tag_entries.emplace_back("triplet", pre_build_info.triplet_abi_tag);
- abi_tag_entries.emplace_back("features", Strings::join(";", config.feature_list));
+ std::vector<std::string> sorted_feature_list(std::begin(config.feature_list), std::end(config.feature_list));
+ Util::sort(sorted_feature_list);
+ abi_tag_entries.emplace_back("features", Strings::join(";", sorted_feature_list));
if (pre_build_info.public_abi_override)
{
@@ -724,6 +709,14 @@ namespace vcpkg::Build Hash::Algorithm::Sha1));
}
+ // No need to sort, the variables are stored in the same order they are written down in the abi-settings file
+ for (const auto& env_var : pre_build_info.passthrough_env_vars)
+ {
+ abi_tag_entries.emplace_back(
+ "ENV:" + env_var,
+ Hash::get_string_hash(System::get_environment_variable(env_var).value_or(""), Hash::Algorithm::Sha1));
+ }
+
if (config.build_package_options.use_head_version == UseHeadVersion::YES)
abi_tag_entries.emplace_back("head", "");
@@ -732,12 +725,13 @@ namespace vcpkg::Build if (Debug::g_debugging)
{
- System::print2("[DEBUG] <abientries>\n");
+ std::string message = "[DEBUG] <abientries>\n";
for (auto&& entry : abi_tag_entries)
{
- System::print2("[DEBUG] ", entry.key, "|", entry.value, "\n");
+ Strings::append(message, "[DEBUG] ", entry.key, "|", entry.value, "\n");
}
- System::print2("[DEBUG] </abientries>\n");
+ Strings::append(message, "[DEBUG] </abientries>\n");
+ System::print2(message);
}
auto abi_tag_entries_missing = abi_tag_entries;
@@ -819,33 +813,27 @@ namespace vcpkg::Build const Triplet& triplet = config.triplet;
const std::string& name = config.scf.core_paragraph->name;
- std::vector<FeatureSpec> required_fspecs = compute_required_feature_specs(config, status_db);
-
- // extract out the actual package ids
- auto dep_pspecs = Util::fmap(required_fspecs, [](FeatureSpec const& fspec) { return fspec.spec(); });
- Util::sort_unique_erase(dep_pspecs);
-
- // Find all features that aren't installed. This mutates required_fspecs.
- // Skip this validation when running in Download Mode.
- if (config.build_package_options.only_downloads != Build::OnlyDownloads::YES)
+ std::vector<FeatureSpec> missing_fspecs;
+ for (const auto& kv : config.feature_dependencies)
{
- Util::erase_remove_if(required_fspecs, [&](FeatureSpec const& fspec) {
- return status_db.is_installed(fspec) || fspec.name() == name;
- });
-
- if (!required_fspecs.empty())
+ for (const FeatureSpec& spec : kv.second)
{
- return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(required_fspecs)};
+ if (!(status_db.is_installed(spec) || spec.name() == name))
+ {
+ missing_fspecs.emplace_back(spec);
+ }
}
}
- const PackageSpec spec =
- PackageSpec::from_name_and_triplet(config.scf.core_paragraph->name, triplet).value_or_exit(VCPKG_LINE_INFO);
+ if (!missing_fspecs.empty())
+ {
+ return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(missing_fspecs)};
+ }
- std::vector<AbiEntry> dependency_abis;
+ const PackageSpec spec = PackageSpec::from_name_and_triplet(name, triplet).value_or_exit(VCPKG_LINE_INFO);
- // dep_pspecs was not destroyed
- for (auto&& pspec : dep_pspecs)
+ std::vector<AbiEntry> dependency_abis;
+ for (auto&& pspec : config.package_dependencies)
{
if (pspec == spec || Util::Enum::to_bool(config.build_package_options.only_downloads))
{
@@ -857,7 +845,9 @@ namespace vcpkg::Build AbiEntry{status_it->get()->package.spec.name(), status_it->get()->package.abi});
}
- const auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet, config.scfl);
+ const std::unordered_map<std::string, std::string>& cmake_vars =
+ config.var_provider.get_tag_vars(spec).value_or_exit(VCPKG_LINE_INFO);
+ const PreBuildInfo pre_build_info(paths, triplet, cmake_vars);
auto maybe_abi_tag_and_file = compute_abi_tag(paths, config, pre_build_info, dependency_abis);
if (!maybe_abi_tag_and_file)
@@ -887,12 +877,22 @@ namespace vcpkg::Build if (archive_result != 0)
{
System::print2("Failed to decompress archive package\n");
- return BuildResult::BUILD_FAILED;
+ if (config.build_package_options.purge_decompress_failure == PurgeDecompressFailure::NO)
+ {
+ return BuildResult::BUILD_FAILED;
+ }
+ else
+ {
+ System::print2("Purging bad archive\n");
+ fs.remove(archive_path, ec);
+ }
+ }
+ else
+ {
+ auto maybe_bcf = Paragraphs::try_load_cached_package(paths, spec);
+ auto bcf = std::make_unique<BinaryControlFile>(std::move(maybe_bcf).value_or_exit(VCPKG_LINE_INFO));
+ return {BuildResult::SUCCEEDED, std::move(bcf)};
}
-
- auto maybe_bcf = Paragraphs::try_load_cached_package(paths, spec);
- auto bcf = std::make_unique<BinaryControlFile>(std::move(maybe_bcf).value_or_exit(VCPKG_LINE_INFO));
- return {BuildResult::SUCCEEDED, std::move(bcf)};
}
if (fs.exists(archive_tombstone_path))
@@ -1077,110 +1077,55 @@ namespace vcpkg::Build return inner_create_buildinfo(*pghs.get());
}
- PreBuildInfo PreBuildInfo::from_triplet_file(const VcpkgPaths& paths,
- const Triplet& triplet,
- Optional<const SourceControlFileLocation&> port)
+ PreBuildInfo::PreBuildInfo(const VcpkgPaths& paths,
+ const Triplet& triplet,
+ const std::unordered_map<std::string, std::string>& cmakevars)
{
- static constexpr CStringView FLAG_GUID = "c35112b6-d1ba-415b-aa5d-81de856ef8eb";
-
- const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE);
- const fs::path ports_cmake_script_path = paths.scripts / "get_triplet_environment.cmake";
- const fs::path triplet_file_path = paths.get_triplet_file_path(triplet);
-
- std::vector<System::CMakeVariable> args{{"CMAKE_TRIPLET_FILE", triplet_file_path}};
-
- if (port)
+ for (auto&& kv : VCPKG_OPTIONS)
{
- const SourceControlFileLocation& scfl = port.value_or_exit(VCPKG_LINE_INFO);
-
- if (paths.get_filesystem().is_regular_file(scfl.source_location / "environment-overrides.cmake"))
+ auto find_itr = cmakevars.find(kv.first);
+ if (find_itr == cmakevars.end())
{
- args.emplace_back("VCPKG_ENV_OVERRIDES_FILE", scfl.source_location / "environment-overrides.cmake");
+ continue;
}
- }
-
- const auto cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path, ports_cmake_script_path, args);
-
- const auto ec_data = System::cmd_execute_and_capture_output(cmd_launch_cmake);
- Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, ec_data.output);
-
- const std::vector<std::string> lines = Strings::split(ec_data.output, "\n");
-
- PreBuildInfo pre_build_info;
-
- pre_build_info.port = port;
-
- const auto e = lines.cend();
- auto cur = std::find(lines.cbegin(), e, FLAG_GUID);
- if (cur != e) ++cur;
-
- for (; cur != e; ++cur)
- {
- auto&& line = *cur;
- const std::vector<std::string> s = Strings::split(line, "=");
- Checks::check_exit(VCPKG_LINE_INFO,
- s.size() == 1 || s.size() == 2,
- "Expected format is [VARIABLE_NAME=VARIABLE_VALUE], but was [%s]",
- line);
+ const std::string& variable_value = find_itr->second;
- const bool variable_with_no_value = s.size() == 1;
- const std::string variable_name = s.at(0);
- const std::string variable_value = variable_with_no_value ? "" : s.at(1);
-
- auto maybe_option = VCPKG_OPTIONS.find(variable_name);
- if (maybe_option != VCPKG_OPTIONS.end())
+ switch (kv.second)
{
- switch (maybe_option->second)
- {
- case VcpkgTripletVar::TARGET_ARCHITECTURE:
- pre_build_info.target_architecture = variable_value;
- break;
- case VcpkgTripletVar::CMAKE_SYSTEM_NAME: pre_build_info.cmake_system_name = variable_value; break;
- case VcpkgTripletVar::CMAKE_SYSTEM_VERSION:
- pre_build_info.cmake_system_version = variable_value;
- break;
- case VcpkgTripletVar::PLATFORM_TOOLSET:
- pre_build_info.platform_toolset =
- variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
- break;
- case VcpkgTripletVar::VISUAL_STUDIO_PATH:
- pre_build_info.visual_studio_path =
- variable_value.empty() ? nullopt : Optional<fs::path>{variable_value};
- break;
- case VcpkgTripletVar::CHAINLOAD_TOOLCHAIN_FILE:
- pre_build_info.external_toolchain_file =
- variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
- break;
- case VcpkgTripletVar::BUILD_TYPE:
- if (variable_value.empty())
- pre_build_info.build_type = nullopt;
- else if (Strings::case_insensitive_ascii_equals(variable_value, "debug"))
- pre_build_info.build_type = ConfigurationType::DEBUG;
- else if (Strings::case_insensitive_ascii_equals(variable_value, "release"))
- pre_build_info.build_type = ConfigurationType::RELEASE;
- else
- Checks::exit_with_message(
- VCPKG_LINE_INFO, "Unknown setting for VCPKG_BUILD_TYPE: %s", variable_value);
- break;
- case VcpkgTripletVar::ENV_PASSTHROUGH:
- pre_build_info.passthrough_env_vars = Strings::split(variable_value, ";");
- break;
- case VcpkgTripletVar::PUBLIC_ABI_OVERRIDE:
- pre_build_info.public_abi_override =
- variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
- break;
- }
- }
- else
- {
- Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown variable name %s", line);
+ case VcpkgTripletVar::TARGET_ARCHITECTURE: target_architecture = variable_value; break;
+ case VcpkgTripletVar::CMAKE_SYSTEM_NAME: cmake_system_name = variable_value; break;
+ case VcpkgTripletVar::CMAKE_SYSTEM_VERSION: cmake_system_version = variable_value; break;
+ case VcpkgTripletVar::PLATFORM_TOOLSET:
+ platform_toolset = variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
+ break;
+ case VcpkgTripletVar::VISUAL_STUDIO_PATH:
+ visual_studio_path = variable_value.empty() ? nullopt : Optional<fs::path>{variable_value};
+ break;
+ case VcpkgTripletVar::CHAINLOAD_TOOLCHAIN_FILE:
+ external_toolchain_file = variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
+ break;
+ case VcpkgTripletVar::BUILD_TYPE:
+ if (variable_value.empty())
+ build_type = nullopt;
+ else if (Strings::case_insensitive_ascii_equals(variable_value, "debug"))
+ build_type = ConfigurationType::DEBUG;
+ else if (Strings::case_insensitive_ascii_equals(variable_value, "release"))
+ build_type = ConfigurationType::RELEASE;
+ else
+ Checks::exit_with_message(
+ VCPKG_LINE_INFO, "Unknown setting for VCPKG_BUILD_TYPE: %s", variable_value);
+ break;
+ case VcpkgTripletVar::ENV_PASSTHROUGH:
+ passthrough_env_vars = Strings::split(variable_value, ";");
+ break;
+ case VcpkgTripletVar::PUBLIC_ABI_OVERRIDE:
+ public_abi_override = variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
+ break;
}
}
- pre_build_info.triplet_abi_tag = get_triplet_abi(paths, pre_build_info, triplet);
-
- return pre_build_info;
+ triplet_abi_tag = get_triplet_abi(paths, *this, triplet);
}
ExtendedBuildResult::ExtendedBuildResult(BuildResult code) : code(code) {}
diff --git a/toolsrc/src/vcpkg/cmakevars.cpp b/toolsrc/src/vcpkg/cmakevars.cpp new file mode 100644 index 000000000..6a9d26ec8 --- /dev/null +++ b/toolsrc/src/vcpkg/cmakevars.cpp @@ -0,0 +1,259 @@ +#include "pch.h" + +#include <vcpkg/base/optional.h> +#include <vcpkg/base/span.h> +#include <vcpkg/base/util.h> + +#include <vcpkg/cmakevars.h> + +using namespace vcpkg; +using vcpkg::Optional; +using vcpkg::CMakeVars::TripletCMakeVarProvider; + +namespace vcpkg::CMakeVars +{ + fs::path TripletCMakeVarProvider::create_tag_extraction_file( + const Span<const std::pair<const FullPackageSpec*, std::string>>& spec_abi_settings) const + { + Files::Filesystem& fs = paths.get_filesystem(); + static int tag_extract_id = 0; + + std::string extraction_file("include(" + get_tags_path.generic_u8string() + ")\n\n"); + + std::map<Triplet, int> emitted_triplets; + int emitted_triplet_id = 0; + for (const auto& spec_abi_setting : spec_abi_settings) + { + emitted_triplets[spec_abi_setting.first->package_spec.triplet()] = emitted_triplet_id++; + } + Strings::append(extraction_file, "macro(vcpkg_triplet_file VCPKG_TRIPLET_ID)\n"); + for (auto& p : emitted_triplets) + { + Strings::append(extraction_file, + "if(VCPKG_TRIPLET_ID EQUAL ", + p.second, + ")\n", + fs.read_contents(paths.get_triplet_file_path(p.first), VCPKG_LINE_INFO), + "\nendif()\n"); + } + Strings::append(extraction_file, "endmacro()\n"); + for (const auto& spec_abi_setting : spec_abi_settings) + { + const FullPackageSpec& spec = *spec_abi_setting.first; + + Strings::append(extraction_file, + "vcpkg_get_tags(\"", + spec.package_spec.name(), + "\" \"", + Strings::join(";", spec.features), + "\" \"", + emitted_triplets[spec.package_spec.triplet()], + "\" \"", + spec_abi_setting.second, + "\")\n"); + } + + fs::path path = paths.buildtrees / Strings::concat(tag_extract_id++, ".vcpkg_tags.cmake"); + + std::error_code ec; + fs.create_directories(paths.buildtrees, ec); + fs.write_contents(path, extraction_file, VCPKG_LINE_INFO); + + return path; + } + + fs::path TripletCMakeVarProvider::create_dep_info_extraction_file(const Span<const PackageSpec> specs) const + { + static int dep_info_id = 0; + Files::Filesystem& fs = paths.get_filesystem(); + + std::string extraction_file("include(" + get_dep_info_path.generic_u8string() + ")\n\n"); + + std::map<Triplet, int> emitted_triplets; + int emitted_triplet_id = 0; + for (const auto& spec : specs) + { + emitted_triplets[spec.triplet()] = emitted_triplet_id++; + } + Strings::append(extraction_file, "macro(vcpkg_triplet_file VCPKG_TRIPLET_ID)\n"); + for (auto& p : emitted_triplets) + { + Strings::append(extraction_file, + "if(VCPKG_TRIPLET_ID EQUAL ", + p.second, + ")\n", + fs.read_contents(paths.get_triplet_file_path(p.first), VCPKG_LINE_INFO), + "\nendif()\n"); + } + Strings::append(extraction_file, "endmacro()\n"); + + for (const PackageSpec& spec : specs) + { + Strings::append( + extraction_file, "vcpkg_get_dep_info(", spec.name(), " ", emitted_triplets[spec.triplet()], ")\n"); + } + + fs::path path = paths.buildtrees / Strings::concat(dep_info_id++, ".vcpkg_dep_info.cmake"); + + std::error_code ec; + fs.create_directories(paths.buildtrees, ec); + fs.write_contents(path, extraction_file, VCPKG_LINE_INFO); + + return path; + } + + void TripletCMakeVarProvider::launch_and_split( + const fs::path& script_path, std::vector<std::vector<std::pair<std::string, std::string>>>& vars) const + { + static constexpr CStringView PORT_START_GUID = "d8187afd-ea4a-4fc3-9aa4-a6782e1ed9af"; + static constexpr CStringView PORT_END_GUID = "8c504940-be29-4cba-9f8f-6cd83e9d87b7"; + static constexpr CStringView BLOCK_START_GUID = "c35112b6-d1ba-415b-aa5d-81de856ef8eb"; + static constexpr CStringView BLOCK_END_GUID = "e1e74b5c-18cb-4474-a6bd-5c1c8bc81f3f"; + + const auto cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path, script_path, {}); + const auto ec_data = System::cmd_execute_and_capture_output(cmd_launch_cmake); + Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, ec_data.output); + + const std::vector<std::string> lines = Strings::split(ec_data.output, "\n"); + + const auto end = lines.cend(); + + auto port_start = std::find(lines.cbegin(), end, PORT_START_GUID); + auto port_end = std::find(port_start, end, PORT_END_GUID); + + for (auto var_itr = vars.begin(); port_start != end && var_itr != vars.end(); ++var_itr) + { + auto block_start = std::find(port_start, port_end, BLOCK_START_GUID); + auto block_end = std::find(++block_start, port_end, BLOCK_END_GUID); + + while (block_start != port_end) + { + while (block_start != block_end) + { + const std::string& line = *block_start; + + std::vector<std::string> s = Strings::split(line, "="); + Checks::check_exit(VCPKG_LINE_INFO, + s.size() == 1 || s.size() == 2, + "Expected format is [VARIABLE_NAME=VARIABLE_VALUE], but was [%s]", + line); + + var_itr->emplace_back(std::move(s[0]), s.size() == 1 ? "" : std::move(s[1])); + + ++block_start; + } + + block_start = std::find(block_end, port_end, BLOCK_START_GUID); + block_end = std::find(block_start, port_end, BLOCK_END_GUID); + } + + port_start = std::find(port_end, end, PORT_START_GUID); + port_end = std::find(port_start, end, PORT_END_GUID); + } + } + + void TripletCMakeVarProvider::load_generic_triplet_vars(const Triplet& triplet) const + { + std::vector<std::vector<std::pair<std::string, std::string>>> vars(1); + FullPackageSpec full_spec = FullPackageSpec::from_string("", triplet).value_or_exit(VCPKG_LINE_INFO); + const fs::path file_path = + create_tag_extraction_file(std::array<std::pair<const FullPackageSpec*, std::string>, 1>{ + std::pair<const FullPackageSpec*, std::string>{&full_spec, ""}}); + launch_and_split(file_path, vars); + paths.get_filesystem().remove(file_path, VCPKG_LINE_INFO); + + generic_triplet_vars[triplet].insert(std::make_move_iterator(vars.front().begin()), + std::make_move_iterator(vars.front().end())); + } + + void TripletCMakeVarProvider::load_dep_info_vars(Span<const PackageSpec> specs) const + { + if (specs.size() == 0) return; + std::vector<std::vector<std::pair<std::string, std::string>>> vars(specs.size()); + const fs::path file_path = create_dep_info_extraction_file(specs); + if (specs.size() > 100) + { + System::print2("Loading dependency information for ", specs.size(), " packages...\n"); + } + launch_and_split(file_path, vars); + paths.get_filesystem().remove(file_path, VCPKG_LINE_INFO); + + auto var_list_itr = vars.begin(); + for (const PackageSpec& spec : specs) + { + dep_resolution_vars.emplace(std::piecewise_construct, + std::forward_as_tuple(spec), + std::forward_as_tuple(std::make_move_iterator(var_list_itr->begin()), + std::make_move_iterator(var_list_itr->end()))); + ++var_list_itr; + } + } + + void TripletCMakeVarProvider::load_tag_vars(Span<const FullPackageSpec> specs, + const PortFileProvider::PortFileProvider& port_provider) const + { + if (specs.size() == 0) return; + std::vector<std::pair<const FullPackageSpec*, std::string>> spec_abi_settings; + spec_abi_settings.reserve(specs.size()); + + for (const FullPackageSpec& spec : specs) + { + auto& scfl = port_provider.get_control_file(spec.package_spec.name()).value_or_exit(VCPKG_LINE_INFO); + const fs::path override_path = scfl.source_location / "vcpkg-abi-settings.cmake"; + spec_abi_settings.emplace_back(&spec, override_path.generic_u8string()); + } + + std::vector<std::vector<std::pair<std::string, std::string>>> vars(spec_abi_settings.size()); + const fs::path file_path = create_tag_extraction_file(spec_abi_settings); + launch_and_split(file_path, vars); + paths.get_filesystem().remove(file_path, VCPKG_LINE_INFO); + + auto var_list_itr = vars.begin(); + for (const auto& spec_abi_setting : spec_abi_settings) + { + const FullPackageSpec& spec = *spec_abi_setting.first; + + tag_vars.emplace(std::piecewise_construct, + std::forward_as_tuple(spec.package_spec), + std::forward_as_tuple(std::make_move_iterator(var_list_itr->begin()), + std::make_move_iterator(var_list_itr->end()))); + ++var_list_itr; + } + } + + Optional<const std::unordered_map<std::string, std::string>&> TripletCMakeVarProvider::get_generic_triplet_vars( + const Triplet& triplet) const + { + auto find_itr = generic_triplet_vars.find(triplet); + if (find_itr != generic_triplet_vars.end()) + { + return find_itr->second; + } + + return nullopt; + } + + Optional<const std::unordered_map<std::string, std::string>&> TripletCMakeVarProvider::get_dep_info_vars( + const PackageSpec& spec) const + { + auto find_itr = dep_resolution_vars.find(spec); + if (find_itr != dep_resolution_vars.end()) + { + return find_itr->second; + } + + return nullopt; + } + + Optional<const std::unordered_map<std::string, std::string>&> TripletCMakeVarProvider::get_tag_vars( + const PackageSpec& spec) const + { + auto find_itr = tag_vars.find(spec); + if (find_itr != tag_vars.end()) + { + return find_itr->second; + } + + return nullopt; + } +} diff --git a/toolsrc/src/vcpkg/commands.autocomplete.cpp b/toolsrc/src/vcpkg/commands.autocomplete.cpp index 8449b7096..2857988ba 100644 --- a/toolsrc/src/vcpkg/commands.autocomplete.cpp +++ b/toolsrc/src/vcpkg/commands.autocomplete.cpp @@ -97,7 +97,7 @@ namespace vcpkg::Commands::Autocomplete Checks::exit_success(VCPKG_LINE_INFO); } - std::vector<std::string> triplets = paths.get_available_triplets(); + std::vector<std::string> triplets = paths.get_available_triplets_names(); Util::erase_remove_if(triplets, [&](const std::string& s) { return !Strings::case_insensitive_ascii_starts_with(s, triplet_prefix); }); @@ -158,8 +158,8 @@ namespace vcpkg::Commands::Autocomplete if (command.name == "install" && results.size() == 1 && !is_option) { const auto port_at_each_triplet = - combine_port_with_triplets(results[0], paths.get_available_triplets()); - Util::Vectors::concatenate(&results, port_at_each_triplet); + combine_port_with_triplets(results[0], paths.get_available_triplets_names()); + Util::Vectors::append(&results, port_at_each_triplet); } output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(results)); diff --git a/toolsrc/src/vcpkg/commands.buildexternal.cpp b/toolsrc/src/vcpkg/commands.buildexternal.cpp index 1c3c511c2..135ef132b 100644 --- a/toolsrc/src/vcpkg/commands.buildexternal.cpp +++ b/toolsrc/src/vcpkg/commands.buildexternal.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include <vcpkg/build.h> +#include <vcpkg/cmakevars.h> #include <vcpkg/commands.h> #include <vcpkg/help.h> #include <vcpkg/input.h> @@ -25,10 +26,13 @@ namespace vcpkg::Commands::BuildExternal auto overlays = args.overlay_ports ? *args.overlay_ports : std::vector<std::string>(); overlays.insert(overlays.begin(), args.command_arguments.at(1)); - Dependencies::PathsPortFileProvider provider(paths, &overlays); + + PortFileProvider::PathsPortFileProvider provider(paths, &overlays); auto maybe_scfl = provider.get_control_file(spec.package_spec.name()); + Checks::check_exit( VCPKG_LINE_INFO, maybe_scfl.has_value(), "could not load control file for %s", spec.package_spec.name()); - Build::Command::perform_and_exit_ex(spec, maybe_scfl.value_or_exit(VCPKG_LINE_INFO), options, paths); + + Build::Command::perform_and_exit_ex(spec, maybe_scfl.value_or_exit(VCPKG_LINE_INFO), provider, options, paths); } } diff --git a/toolsrc/src/vcpkg/commands.ci.cpp b/toolsrc/src/vcpkg/commands.ci.cpp index 419d65f03..b8ac97563 100644 --- a/toolsrc/src/vcpkg/commands.ci.cpp +++ b/toolsrc/src/vcpkg/commands.ci.cpp @@ -13,8 +13,12 @@ #include <vcpkg/help.h> #include <vcpkg/input.h> #include <vcpkg/install.h> +#include <vcpkg/logicexpression.h> +#include <vcpkg/packagespec.h> #include <vcpkg/vcpkglib.h> +using namespace vcpkg; + namespace vcpkg::Commands::CI { using Build::BuildResult; @@ -178,7 +182,7 @@ namespace vcpkg::Commands::CI std::string feature_list; for (const auto& feature : test.features) { - if (!feature_list.empty()) + if (!feature_list.empty()) { feature_list += ", "; } @@ -215,14 +219,44 @@ namespace vcpkg::Commands::CI std::vector<FullPackageSpec> unknown; std::map<PackageSpec, Build::BuildResult> known; std::map<PackageSpec, std::vector<std::string>> features; + std::unordered_map<std::string, SourceControlFileLocation> default_feature_provider; std::map<PackageSpec, std::string> abi_tag_map; }; + static bool supported_for_triplet(const CMakeVars::TripletCMakeVarProvider& var_provider, + + const InstallPlanAction* install_plan) + { + const std::string& supports_expression = + install_plan->source_control_file_location.value_or_exit(VCPKG_LINE_INFO) + .source_control_file->core_paragraph->supports_expression; + if (supports_expression.empty()) + { + return true; // default to 'supported' + } + + ExpressionContext context = {var_provider.get_tag_vars(install_plan->spec).value_or_exit(VCPKG_LINE_INFO), + install_plan->spec.triplet().canonical_name()}; + auto maybe_result = evaluate_expression(supports_expression, context); + if (auto b = maybe_result.get()) + return *b; + else + { + System::print2(System::Color::error, + "Error: while processing ", + install_plan->spec.to_string(), + "\n", + maybe_result.error()); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + static std::unique_ptr<UnknownCIPortsResults> find_unknown_ports_for_ci( const VcpkgPaths& paths, const std::set<std::string>& exclusions, - const Dependencies::PortFileProvider& provider, - const std::vector<FeatureSpec>& fspecs, + const PortFileProvider::PortFileProvider& provider, + const CMakeVars::TripletCMakeVarProvider& var_provider, + const std::vector<FullPackageSpec>& specs, const bool purge_tombstones) { auto ret = std::make_unique<UnknownCIPortsResults>(); @@ -243,101 +277,149 @@ namespace vcpkg::Commands::CI Build::FailOnTombstone::YES, }; - vcpkg::Cache<Triplet, Build::PreBuildInfo> pre_build_info_cache; - - auto action_plan = Dependencies::create_feature_install_plan(provider, fspecs, {}, {}); - - auto timer = Chrono::ElapsedTimer::create_started(); - - for (auto&& action : action_plan) + std::vector<PackageSpec> packages_with_qualified_deps; + auto has_qualifier = [](Dependency const& dep) { return !dep.qualifier.empty(); }; + for (auto&& spec : specs) { - if (auto p = action.install_action.get()) + auto&& scfl = provider.get_control_file(spec.package_spec.name()).value_or_exit(VCPKG_LINE_INFO); + if (Util::any_of(scfl.source_control_file->core_paragraph->depends, has_qualifier) || + Util::any_of(scfl.source_control_file->feature_paragraphs, + [&](auto&& pgh) { return Util::any_of(pgh->depends, has_qualifier); })) { - // determine abi tag - std::string abi; - if (auto scfl = p->source_control_file_location.get()) - { - auto triplet = p->spec.triplet(); + packages_with_qualified_deps.push_back(spec.package_spec); + } + } + + var_provider.load_dep_info_vars(packages_with_qualified_deps); + auto action_plan = Dependencies::create_feature_install_plan(provider, var_provider, specs, {}, {}); - const Build::BuildPackageConfig build_config{*scfl, triplet, build_options, p->feature_list}; + std::vector<FullPackageSpec> install_specs; + for (auto&& install_action : action_plan.install_actions) + { + install_specs.emplace_back(FullPackageSpec{ + install_action.spec, + std::vector<std::string>{install_action.feature_list.begin(), install_action.feature_list.end()}}); + } - auto dependency_abis = - Util::fmap(p->computed_dependencies, [&](const PackageSpec& spec) -> Build::AbiEntry { - auto it = ret->abi_tag_map.find(spec); + var_provider.load_tag_vars(install_specs, provider); - if (it == ret->abi_tag_map.end()) - return {spec.name(), ""}; - else - return {spec.name(), it->second}; - }); - const auto& pre_build_info = pre_build_info_cache.get_lazy( - triplet, [&]() { return Build::PreBuildInfo::from_triplet_file(paths, triplet, *scfl); }); + auto timer = Chrono::ElapsedTimer::create_started(); - auto maybe_tag_and_file = - Build::compute_abi_tag(paths, build_config, pre_build_info, dependency_abis); - if (auto tag_and_file = maybe_tag_and_file.get()) - { - abi = tag_and_file->tag; - ret->abi_tag_map.emplace(p->spec, abi); - } - } - else if (auto ipv = p->installed_package.get()) + Checks::check_exit(VCPKG_LINE_INFO, + action_plan.already_installed.empty(), + "Cannot use CI command with packages already installed."); + std::string stdout_buffer; + for (auto&& action : action_plan.install_actions) + { + auto p = &action; + // determine abi tag + std::string abi; + if (auto scfl = p->source_control_file_location.get()) + { + auto emp = ret->default_feature_provider.emplace(p->spec.name(), *scfl); + emp.first->second.source_control_file->core_paragraph->default_features = p->feature_list; + + auto triplet = p->spec.triplet(); + + const Build::BuildPackageConfig build_config{*scfl, + triplet, + build_options, + var_provider, + p->feature_dependencies, + p->package_dependencies, + p->feature_list}; + + auto dependency_abis = + Util::fmap(build_config.package_dependencies, [&](const PackageSpec& spec) -> Build::AbiEntry { + auto it = ret->abi_tag_map.find(spec); + + if (it == ret->abi_tag_map.end()) + return {spec.name(), ""}; + else + return {spec.name(), it->second}; + }); + + const auto pre_build_info = Build::PreBuildInfo( + paths, triplet, var_provider.get_tag_vars(p->spec).value_or_exit(VCPKG_LINE_INFO)); + + auto maybe_tag_and_file = Build::compute_abi_tag(paths, build_config, pre_build_info, dependency_abis); + if (auto tag_and_file = maybe_tag_and_file.get()) { - abi = ipv->core->package.abi; - if (!abi.empty()) ret->abi_tag_map.emplace(p->spec, abi); + abi = tag_and_file->tag; + ret->abi_tag_map.emplace(p->spec, abi); } + } + else if (auto ipv = p->installed_package.get()) + { + abi = ipv->core->package.abi; + if (!abi.empty()) ret->abi_tag_map.emplace(p->spec, abi); + } - std::string state; - - auto archives_root_dir = paths.root / "archives"; - auto archive_name = abi + ".zip"; - auto archive_subpath = fs::u8path(abi.substr(0, 2)) / archive_name; - auto archive_path = archives_root_dir / archive_subpath; - auto archive_tombstone_path = archives_root_dir / "fail" / archive_subpath; + auto archives_root_dir = paths.root / "archives"; + auto archive_name = abi + ".zip"; + auto archive_subpath = fs::u8path(abi.substr(0, 2)) / archive_name; + auto archive_path = archives_root_dir / archive_subpath; + auto archive_tombstone_path = archives_root_dir / "fail" / archive_subpath; - if (purge_tombstones) - { - std::error_code ec; - fs.remove(archive_tombstone_path, ec); // Ignore error - } + if (purge_tombstones) + { + std::error_code ec; + fs.remove(archive_tombstone_path, ec); // Ignore error + } - bool b_will_build = false; + bool b_will_build = false; - ret->features.emplace(p->spec, - std::vector<std::string>{p->feature_list.begin(), p->feature_list.end()}); + std::string state; - if (Util::Sets::contains(exclusions, p->spec.name())) - { - ret->known.emplace(p->spec, BuildResult::EXCLUDED); - will_fail.emplace(p->spec); - } - else if (std::any_of(p->computed_dependencies.begin(), - p->computed_dependencies.end(), - [&](const PackageSpec& spec) { return Util::Sets::contains(will_fail, spec); })) - { - ret->known.emplace(p->spec, BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES); - will_fail.emplace(p->spec); - } - else if (fs.exists(archive_path)) - { - state += "pass"; - ret->known.emplace(p->spec, BuildResult::SUCCEEDED); - } - else if (fs.exists(archive_tombstone_path)) - { - state += "fail"; - ret->known.emplace(p->spec, BuildResult::BUILD_FAILED); - will_fail.emplace(p->spec); - } - else - { - ret->unknown.push_back({p->spec, {p->feature_list.begin(), p->feature_list.end()}}); - b_will_build = true; - } + if (Util::Sets::contains(exclusions, p->spec.name())) + { + state = "skip"; + ret->known.emplace(p->spec, BuildResult::EXCLUDED); + will_fail.emplace(p->spec); + } + else if (!supported_for_triplet(var_provider, p)) + { + // This treats unsupported ports as if they are excluded + // which means the ports dependent on it will be cascaded due to missing dependencies + // Should this be changed so instead it is a failure to depend on a unsupported port? + state = "n/a"; + ret->known.emplace(p->spec, BuildResult::EXCLUDED); + will_fail.emplace(p->spec); + } + else if (Util::any_of(p->package_dependencies, + [&](const PackageSpec& spec) { return Util::Sets::contains(will_fail, spec); })) + { + state = "cascade"; + ret->known.emplace(p->spec, BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES); + will_fail.emplace(p->spec); + } + else if (fs.exists(archive_path)) + { + state = "pass"; + ret->known.emplace(p->spec, BuildResult::SUCCEEDED); + } + else if (fs.exists(archive_tombstone_path)) + { + state = "fail"; + ret->known.emplace(p->spec, BuildResult::BUILD_FAILED); + will_fail.emplace(p->spec); + } + else + { + ret->unknown.push_back(FullPackageSpec{p->spec, {p->feature_list.begin(), p->feature_list.end()}}); + b_will_build = true; + } - System::printf("%40s: %1s %8s: %s\n", p->spec, (b_will_build ? "*" : " "), state, abi); + Strings::append(stdout_buffer, + Strings::format("%40s: %1s %8s: %s\n", p->spec, (b_will_build ? "*" : " "), state, abi)); + if (stdout_buffer.size() > 2048) + { + System::print2(stdout_buffer); + stdout_buffer.clear(); } } + System::print2(stdout_buffer); + stdout_buffer.clear(); System::printf("Time to determine pass/fail: %s\n", timer.elapsed()); @@ -374,7 +456,8 @@ namespace vcpkg::Commands::CI StatusParagraphs status_db = database_load_check(paths); - Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + CMakeVars::TripletCMakeVarProvider var_provider(paths); const Build::BuildPackageOptions install_plan_options = { Build::UseHeadVersion::NO, @@ -386,6 +469,7 @@ namespace vcpkg::Commands::CI Build::DownloadTool::BUILT_IN, GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, Build::FailOnTombstone::YES, + Build::PurgeDecompressFailure::YES, }; std::vector<std::map<PackageSpec, BuildResult>> all_known_results; @@ -394,7 +478,7 @@ namespace vcpkg::Commands::CI XunitTestResults xunitTestResults; std::vector<std::string> all_ports = - Util::fmap(provider.load_all_control_files(), [](auto&& scfl) -> std::string { + Util::fmap(provider.load_all_control_files(), [](const SourceControlFileLocation*& scfl) -> std::string { return scfl->source_control_file.get()->core_paragraph->name; }); std::vector<TripletAndSummary> results; @@ -405,17 +489,18 @@ namespace vcpkg::Commands::CI xunitTestResults.push_collection(triplet.canonical_name()); - Dependencies::PackageGraph pgraph(provider, status_db); - std::vector<PackageSpec> specs = PackageSpec::to_package_specs(all_ports, triplet); // Install the default features for every package - auto all_feature_specs = Util::fmap(specs, [](auto& spec) { return FeatureSpec(spec, ""); }); - auto split_specs = - find_unknown_ports_for_ci(paths, exclusions_set, provider, all_feature_specs, purge_tombstones); - auto feature_specs = FullPackageSpec::to_feature_specs(split_specs->unknown); + auto all_default_full_specs = Util::fmap(specs, [&](auto& spec) { + std::vector<std::string> default_features = + provider.get_control_file(spec.name()).get()->source_control_file->core_paragraph->default_features; + default_features.emplace_back("core"); + return FullPackageSpec{spec, std::move(default_features)}; + }); - for (auto&& fspec : feature_specs) - pgraph.install(fspec); + auto split_specs = find_unknown_ports_for_ci( + paths, exclusions_set, provider, var_provider, all_default_full_specs, purge_tombstones); + PortFileProvider::MapPortFileProvider new_default_provider(split_specs->default_feature_provider); Dependencies::CreateInstallPlanOptions serialize_options; @@ -436,36 +521,20 @@ namespace vcpkg::Commands::CI serialize_options.randomizer = &randomizer_instance; } - auto action_plan = [&]() { - int iterations = 0; - do - { - bool inconsistent = false; - auto action_plan = pgraph.serialize(serialize_options); - - for (auto&& action : action_plan) - { - if (auto p = action.install_action.get()) - { - p->build_options = install_plan_options; - if (Util::Sets::contains(exclusions_set, p->spec.name())) - { - p->plan_type = InstallPlanType::EXCLUDED; - } - - for (auto&& feature : split_specs->features[p->spec]) - if (p->feature_list.find(feature) == p->feature_list.end()) - { - pgraph.install({p->spec, feature}); - inconsistent = true; - } - } - } + auto action_plan = Dependencies::create_feature_install_plan( + new_default_provider, var_provider, split_specs->unknown, status_db, serialize_options); - if (!inconsistent) return action_plan; - Checks::check_exit(VCPKG_LINE_INFO, ++iterations < 100); - } while (true); - }(); + for (auto&& action : action_plan.install_actions) + { + if (Util::Sets::contains(exclusions_set, action.spec.name())) + { + action.plan_type = InstallPlanType::EXCLUDED; + } + else + { + action.build_options = install_plan_options; + } + } if (is_dry_run) { @@ -474,7 +543,7 @@ namespace vcpkg::Commands::CI else { auto collection_timer = Chrono::ElapsedTimer::create_started(); - auto summary = Install::perform(action_plan, Install::KeepGoing::YES, paths, status_db); + auto summary = Install::perform(action_plan, Install::KeepGoing::YES, paths, status_db, var_provider); auto collection_time_elapsed = collection_timer.elapsed(); // Adding results for ports that were built or pulled from an archive diff --git a/toolsrc/src/vcpkg/commands.dependinfo.cpp b/toolsrc/src/vcpkg/commands.dependinfo.cpp index 79cbba590..eecc45634 100644 --- a/toolsrc/src/vcpkg/commands.dependinfo.cpp +++ b/toolsrc/src/vcpkg/commands.dependinfo.cpp @@ -11,10 +11,9 @@ #include <vcpkg/packagespec.h>
#include <vector>
-using vcpkg::Dependencies::AnyAction;
-using vcpkg::Dependencies::create_feature_install_plan;
+using vcpkg::Dependencies::ActionPlan;
using vcpkg::Dependencies::InstallPlanAction;
-using vcpkg::Dependencies::PathsPortFileProvider;
+using vcpkg::PortFileProvider::PathsPortFileProvider;
namespace vcpkg::Commands::DependInfo
{
@@ -44,7 +43,7 @@ namespace vcpkg::Commands::DependInfo {
std::string package;
int depth;
- std::set<std::string> features;
+ std::unordered_set<std::string> features;
std::vector<std::string> dependencies;
};
@@ -205,9 +204,10 @@ namespace vcpkg::Commands::DependInfo const InstallPlanAction& install_action = *pia;
const std::vector<std::string> dependencies = Util::fmap(
- install_action.computed_dependencies, [](const PackageSpec& spec) { return spec.name(); });
+ install_action.package_dependencies, [](const PackageSpec& spec) { return spec.name(); });
- std::set<std::string> features{install_action.feature_list};
+ std::unordered_set<std::string> features{install_action.feature_list.begin(),
+ install_action.feature_list.end()};
features.erase("core");
std::string port_name = install_action.spec.name();
@@ -252,19 +252,18 @@ namespace vcpkg::Commands::DependInfo }
PathsPortFileProvider provider(paths, args.overlay_ports.get());
+ CMakeVars::TripletCMakeVarProvider var_provider(paths);
// By passing an empty status_db, we should get a plan containing all dependencies.
// All actions in the plan should be install actions, as there's no installed packages to remove.
StatusParagraphs status_db;
- std::vector<AnyAction> action_plan =
- create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db);
- std::vector<const InstallPlanAction*> install_actions = Util::fmap(action_plan, [&](const AnyAction& action) {
- if (auto install_action = action.install_action.get())
- {
- return install_action;
- }
- Checks::exit_with_message(VCPKG_LINE_INFO, "Only install actions should exist in the plan");
- });
+ auto action_plan = Dependencies::create_feature_install_plan(provider, var_provider, specs, status_db);
+ Checks::check_exit(
+ VCPKG_LINE_INFO, action_plan.remove_actions.empty(), "Only install actions should exist in the plan");
+ std::vector<const InstallPlanAction*> install_actions =
+ Util::fmap(action_plan.already_installed, [&](const auto& action) { return &action; });
+ for (auto&& action : action_plan.install_actions)
+ install_actions.push_back(&action);
std::vector<PackageDependInfo> depend_info = extract_depend_info(install_actions, max_depth);
diff --git a/toolsrc/src/vcpkg/commands.env.cpp b/toolsrc/src/vcpkg/commands.env.cpp index b81aeae55..c54f73e76 100644 --- a/toolsrc/src/vcpkg/commands.env.cpp +++ b/toolsrc/src/vcpkg/commands.env.cpp @@ -2,7 +2,9 @@ #include <vcpkg/base/strings.h> #include <vcpkg/base/system.process.h> + #include <vcpkg/build.h> +#include <vcpkg/cmakevars.h> #include <vcpkg/commands.h> #include <vcpkg/help.h> @@ -30,13 +32,20 @@ namespace vcpkg::Commands::Env nullptr, }; + // This command should probably optionally take a port void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& triplet) { const auto& fs = paths.get_filesystem(); const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); - const auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, triplet); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + CMakeVars::TripletCMakeVarProvider var_provider(paths); + + var_provider.load_generic_triplet_vars(triplet); + + const Build::PreBuildInfo pre_build_info( + paths, triplet, var_provider.get_generic_triplet_vars(triplet).value_or_exit(VCPKG_LINE_INFO)); const Toolset& toolset = paths.get_toolset(pre_build_info); auto env_cmd = Build::make_build_env_cmd(pre_build_info, toolset); diff --git a/toolsrc/src/vcpkg/commands.search.cpp b/toolsrc/src/vcpkg/commands.search.cpp index 943233502..372c394fa 100644 --- a/toolsrc/src/vcpkg/commands.search.cpp +++ b/toolsrc/src/vcpkg/commands.search.cpp @@ -9,7 +9,7 @@ #include <vcpkg/sourceparagraph.h> #include <vcpkg/vcpkglib.h> -using vcpkg::Dependencies::PathsPortFileProvider; +using vcpkg::PortFileProvider::PathsPortFileProvider; namespace vcpkg::Commands::Search { diff --git a/toolsrc/src/vcpkg/commands.upgrade.cpp b/toolsrc/src/vcpkg/commands.upgrade.cpp index b1dbf6194..e8afabed5 100644 --- a/toolsrc/src/vcpkg/commands.upgrade.cpp +++ b/toolsrc/src/vcpkg/commands.upgrade.cpp @@ -44,8 +44,8 @@ namespace vcpkg::Commands::Upgrade StatusParagraphs status_db = database_load_check(paths); // Load ports from ports dirs - Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); - Dependencies::PackageGraph graph(provider, status_db); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + CMakeVars::TripletCMakeVarProvider var_provider(paths); // input sanitization const std::vector<PackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { @@ -57,6 +57,7 @@ namespace vcpkg::Commands::Upgrade Input::check_triplet(spec.triplet(), paths); } + Dependencies::ActionPlan action_plan; if (specs.empty()) { // If no packages specified, upgrade all outdated packages. @@ -68,8 +69,11 @@ namespace vcpkg::Commands::Upgrade Checks::exit_success(VCPKG_LINE_INFO); } - for (auto&& outdated_package : outdated_packages) - graph.upgrade(outdated_package.spec); + action_plan = Dependencies::create_upgrade_plan( + provider, + var_provider, + Util::fmap(outdated_packages, [](const Update::OutdatedPackage& package) { return package.spec; }), + status_db); } else { @@ -143,13 +147,10 @@ namespace vcpkg::Commands::Upgrade if (to_upgrade.empty()) Checks::exit_success(VCPKG_LINE_INFO); - for (auto&& spec : to_upgrade) - graph.upgrade(spec); + action_plan = Dependencies::create_upgrade_plan(provider, var_provider, to_upgrade, status_db); } - auto plan = graph.serialize(); - - Checks::check_exit(VCPKG_LINE_INFO, !plan.empty()); + Checks::check_exit(VCPKG_LINE_INFO, !action_plan.empty()); const Build::BuildPackageOptions install_plan_options = { Build::UseHeadVersion::NO, @@ -164,15 +165,12 @@ namespace vcpkg::Commands::Upgrade }; // Set build settings for all install actions - for (auto&& action : plan) + for (auto&& action : action_plan.install_actions) { - if (auto p_install = action.install_action.get()) - { - p_install->build_options = install_plan_options; - } + action.build_options = install_plan_options; } - Dependencies::print_plan(plan, true, paths.ports); + Dependencies::print_plan(action_plan, true, paths.ports); if (!no_dry_run) { @@ -182,7 +180,8 @@ namespace vcpkg::Commands::Upgrade Checks::exit_fail(VCPKG_LINE_INFO); } - const Install::InstallSummary summary = Install::perform(plan, keep_going, paths, status_db); + const Install::InstallSummary summary = + Install::perform(action_plan, keep_going, paths, status_db, var_provider); System::print2("\nTotal elapsed time: ", summary.total_elapsed_time, "\n\n"); diff --git a/toolsrc/src/vcpkg/dependencies.cpp b/toolsrc/src/vcpkg/dependencies.cpp index 7c3f2df4c..a395b167f 100644 --- a/toolsrc/src/vcpkg/dependencies.cpp +++ b/toolsrc/src/vcpkg/dependencies.cpp @@ -7,25 +7,40 @@ #include <vcpkg/dependencies.h> #include <vcpkg/packagespec.h> #include <vcpkg/paragraphs.h> +#include <vcpkg/portfileprovider.h> #include <vcpkg/statusparagraphs.h> #include <vcpkg/vcpkglib.h> #include <vcpkg/vcpkgpaths.h> +using namespace vcpkg; + namespace vcpkg::Dependencies { namespace { struct ClusterInstalled { + ClusterInstalled(const InstalledPackageView& ipv) : ipv(ipv) + { + original_features.emplace("core"); + for (auto&& feature : ipv.features) + { + original_features.emplace(feature->package.feature); + } + } + InstalledPackageView ipv; - std::set<PackageSpec> remove_edges; - std::set<std::string> original_features; + std::unordered_set<PackageSpec> remove_edges; + std::unordered_set<std::string> original_features; + + // Tracks whether an incoming request has asked for the default features -- on reinstall, add them + bool defaults_requested = false; }; - struct ClusterSource + struct ClusterInstallInfo { - const SourceControlFileLocation* scfl = nullptr; std::unordered_map<std::string, std::vector<FeatureSpec>> build_edges; + bool defaults_requested = false; }; /// <summary> @@ -33,56 +48,189 @@ namespace vcpkg::Dependencies /// </summary> struct Cluster : Util::MoveOnlyBase { - PackageSpec spec; + Cluster(const InstalledPackageView& ipv, const SourceControlFileLocation& scfl) + : m_spec(ipv.spec()), m_scfl(scfl), m_installed(ipv) + { + } + + Cluster(const PackageSpec& spec, const SourceControlFileLocation& scfl) : m_spec(spec), m_scfl(scfl) {} + + bool has_feature_installed(const std::string& feature) const + { + if (const ClusterInstalled* inst = m_installed.get()) + { + return inst->original_features.find(feature) != inst->original_features.end(); + } + return false; + } + + bool has_defaults_installed() const + { + if (const ClusterInstalled* inst = m_installed.get()) + { + return std::all_of(inst->ipv.core->package.default_features.begin(), + inst->ipv.core->package.default_features.end(), + [&](const std::string& feature) { + return inst->original_features.find(feature) != + inst->original_features.end(); + }); + } + return false; + } + + // Returns dependencies which were added as a result of this call. + // Precondition: must have called "mark_for_reinstall()" or "create_install_info()" on this cluster + void add_feature(const std::string& feature, + const CMakeVars::CMakeVarProvider& var_provider, + std::vector<FeatureSpec>& out_new_dependencies) + { + ClusterInstallInfo& info = m_install_info.value_or_exit(VCPKG_LINE_INFO); + if (feature == "default") + { + if (!info.defaults_requested) + { + info.defaults_requested = true; + for (auto&& f : m_scfl.source_control_file->core_paragraph->default_features) + out_new_dependencies.emplace_back(m_spec, f); + } + return; + } + + if (Util::Sets::contains(info.build_edges, feature)) + { + // This feature has already been completely handled + return; + } + auto maybe_vars = var_provider.get_dep_info_vars(m_spec); + const std::vector<Dependency>* qualified_deps = + &m_scfl.source_control_file->find_dependencies_for_feature(feature).value_or_exit(VCPKG_LINE_INFO); + + std::vector<FeatureSpec> dep_list; + if (maybe_vars) + { + // Qualified dependency resolution is available + auto fullspec_list = filter_dependencies( + *qualified_deps, m_spec.triplet(), maybe_vars.value_or_exit(VCPKG_LINE_INFO)); + + for (auto&& fspec : fullspec_list) + { + // TODO: this is incorrect and does not handle default features nor "*" + Util::Vectors::append(&dep_list, fspec.to_feature_specs({"default"}, {})); + } + + Util::sort_unique_erase(dep_list); + info.build_edges.emplace(feature, dep_list); + } + else + { + bool requires_qualified_resolution = false; + for (const Dependency& dep : *qualified_deps) + { + if (dep.qualifier.empty()) + { + Util::Vectors::append( + &dep_list, + FullPackageSpec(PackageSpec::from_name_and_triplet(dep.depend.name, m_spec.triplet()) + .value_or_exit(VCPKG_LINE_INFO), + dep.depend.features) + .to_feature_specs({"default"}, {})); + } + else + { + requires_qualified_resolution = true; + } + } + Util::sort_unique_erase(dep_list); + if (!requires_qualified_resolution) + { + info.build_edges.emplace(feature, dep_list); + } + } + Util::Vectors::append(&out_new_dependencies, dep_list); + } + + void create_install_info(std::vector<FeatureSpec>& out_reinstall_requirements) + { + bool defaults_requested = false; + if (const ClusterInstalled* inst = m_installed.get()) + { + for (const std::string& installed_feature : inst->original_features) + { + out_reinstall_requirements.emplace_back(m_spec, installed_feature); + } + defaults_requested = inst->defaults_requested; + } + + Checks::check_exit(VCPKG_LINE_INFO, !m_install_info.has_value()); + m_install_info = make_optional(ClusterInstallInfo{}); + + if (defaults_requested) + { + for (auto&& def_feature : m_scfl.source_control_file->core_paragraph->default_features) + out_reinstall_requirements.emplace_back(m_spec, def_feature); + } + else if (request_type != RequestType::USER_REQUESTED) + { + // If the user did not explicitly request this installation, we need to add all new default features + auto&& new_defaults = m_scfl.source_control_file->core_paragraph->default_features; + std::set<std::string> defaults_set{new_defaults.begin(), new_defaults.end()}; + + // Install only features that were not previously available + if (auto p_inst = m_installed.get()) + { + for (auto&& prev_default : p_inst->ipv.core->package.default_features) + { + defaults_set.erase(prev_default); + } + } + + for (const std::string& feature : defaults_set) + { + // Instead of dealing with adding default features to each of our dependencies right + // away we just defer to the next pass of the loop. + out_reinstall_requirements.emplace_back(m_spec, feature); + } + } + } + + PackageSpec m_spec; + const SourceControlFileLocation& m_scfl; - Optional<ClusterInstalled> installed; - Optional<ClusterSource> source; + Optional<ClusterInstalled> m_installed; + Optional<ClusterInstallInfo> m_install_info; - // Note: this map can contain "special" strings such as "" and "*" - std::unordered_map<std::string, bool> plus; - std::set<std::string> to_install_features; - bool minus = false; - bool transient_uninstalled = true; RequestType request_type = RequestType::AUTO_SELECTED; }; - struct ClusterPtr + struct PackageGraph { - Cluster* ptr; + PackageGraph(const PortFileProvider::PortFileProvider& provider, + const CMakeVars::CMakeVarProvider& var_provider, + const StatusParagraphs& status_db); + ~PackageGraph(); - Cluster* operator->() const { return ptr; } - }; + void install(Span<const FeatureSpec> specs); + void upgrade(Span<const PackageSpec> specs); + void mark_user_requested(const PackageSpec& spec); - bool operator==(const ClusterPtr& l, const ClusterPtr& r) { return l.ptr == r.ptr; } - } -} + ActionPlan serialize(const CreateInstallPlanOptions& options = {}) const; -namespace std -{ - template<> - struct hash<vcpkg::Dependencies::ClusterPtr> - { - size_t operator()(const vcpkg::Dependencies::ClusterPtr& value) const - { - return std::hash<vcpkg::PackageSpec>()(value.ptr->spec); - } - }; -} + void mark_for_reinstall(const PackageSpec& spec, std::vector<FeatureSpec>& out_reinstall_requirements); + const CMakeVars::CMakeVarProvider& m_var_provider; -namespace vcpkg::Dependencies -{ - struct GraphPlan - { - Graphs::Graph<ClusterPtr> remove_graph; - Graphs::Graph<ClusterPtr> install_graph; - }; + std::unique_ptr<ClusterGraph> m_graph; + }; + + } /// <summary> /// Directional graph representing a collection of packages with their features connected by their dependencies. /// </summary> struct ClusterGraph : Util::MoveOnlyBase { - explicit ClusterGraph(const PortFileProvider& provider) : m_provider(provider) {} + explicit ClusterGraph(const PortFileProvider::PortFileProvider& port_provider) : m_port_provider(port_provider) + { + } /// <summary> /// Find the cluster associated with spec or if not found, create it from the PortFileProvider. @@ -94,35 +242,64 @@ namespace vcpkg::Dependencies auto it = m_graph.find(spec); if (it == m_graph.end()) { - // Load on-demand from m_provider - auto maybe_scfl = m_provider.get_control_file(spec.name()); - auto& clust = m_graph[spec]; - clust.spec = spec; - if (auto p_scfl = maybe_scfl.get()) - { - clust.source = cluster_from_scf(*p_scfl, clust.spec.triplet()); - } - return clust; + const SourceControlFileLocation* scfl = m_port_provider.get_control_file(spec.name()).get(); + + Checks::check_exit( + VCPKG_LINE_INFO, scfl, "Error: Cannot find definition for package `%s`.", spec.name()); + + return m_graph + .emplace(std::piecewise_construct, std::forward_as_tuple(spec), std::forward_as_tuple(spec, *scfl)) + .first->second; } + return it->second; } - private: - static ClusterSource cluster_from_scf(const SourceControlFileLocation& scfl, Triplet t) + Cluster& get(const InstalledPackageView& ipv) { - ClusterSource ret; - ret.build_edges.emplace("core", - filter_dependencies_to_specs(scfl.source_control_file->core_paragraph->depends, t)); + auto it = m_graph.find(ipv.spec()); + + if (it == m_graph.end()) + { + Optional<const SourceControlFileLocation&> maybe_scfl = + m_port_provider.get_control_file(ipv.spec().name()); + + if (!maybe_scfl) + Checks::exit_with_message(VCPKG_LINE_INFO, + "We could not find a CONTROL file for ", + ipv.spec().to_string(), + ". Please run \"vcpkg remove ", + ipv.spec().to_string(), + "\" and re-attempt."); + + return m_graph + .emplace(std::piecewise_construct, + std::forward_as_tuple(ipv.spec()), + std::forward_as_tuple(ipv, *maybe_scfl.get())) + .first->second; + } + + if (!it->second.m_installed) + { + it->second.m_installed = {ipv}; + } - for (const auto& feature : scfl.source_control_file->feature_paragraphs) - ret.build_edges.emplace(feature->name, filter_dependencies_to_specs(feature->depends, t)); + return it->second; + } - ret.scfl = &scfl; - return ret; + const Cluster& find_or_exit(const PackageSpec& spec, LineInfo linfo) const + { + auto it = m_graph.find(spec); + Checks::check_exit(linfo, it != m_graph.end(), "Failed to locate spec in graph"); + return it->second; } + auto begin() const { return m_graph.begin(); } + auto end() const { return m_graph.end(); } + + private: std::unordered_map<PackageSpec, Cluster> m_graph; - const PortFileProvider& m_provider; + const PortFileProvider::PortFileProvider& m_port_provider; }; static std::string to_output_string(RequestType request_type, @@ -178,30 +355,44 @@ namespace vcpkg::Dependencies InstallPlanAction::InstallPlanAction(const PackageSpec& spec, const SourceControlFileLocation& scfl, - const std::set<std::string>& features, const RequestType& request_type, - std::vector<PackageSpec>&& dependencies) + std::unordered_map<std::string, std::vector<FeatureSpec>>&& dependencies) : spec(spec) , source_control_file_location(scfl) , plan_type(InstallPlanType::BUILD_AND_INSTALL) , request_type(request_type) , build_options{} - , feature_list(features) - , computed_dependencies(std::move(dependencies)) + , feature_dependencies(std::move(dependencies)) { + for (const auto& kv : feature_dependencies) + { + feature_list.emplace_back(kv.first); + for (const FeatureSpec& fspec : kv.second) + { + if (spec != fspec.spec()) + { + package_dependencies.emplace_back(fspec.spec()); + } + } + } + + Util::sort_unique_erase(package_dependencies); + Util::sort_unique_erase(feature_list); } - InstallPlanAction::InstallPlanAction(InstalledPackageView&& ipv, - const std::set<std::string>& features, - const RequestType& request_type) + InstallPlanAction::InstallPlanAction(InstalledPackageView&& ipv, const RequestType& request_type) : spec(ipv.spec()) , installed_package(std::move(ipv)) , plan_type(InstallPlanType::ALREADY_INSTALLED) , request_type(request_type) , build_options{} - , feature_list(features) - , computed_dependencies(installed_package.get()->dependencies()) + , feature_dependencies(installed_package.get()->feature_dependencies()) + , package_dependencies(installed_package.get()->dependencies()) { + for (const auto& kv : feature_dependencies) + { + feature_list.emplace_back(kv.first); + } } std::string InstallPlanAction::displayname() const @@ -211,7 +402,7 @@ namespace vcpkg::Dependencies return this->spec.to_string(); } - const std::string features = Strings::join(",", this->feature_list); + const std::string features = Strings::join(",", feature_list); return Strings::format("%s[%s]:%s", this->spec.name(), features, this->spec.triplet()); } @@ -232,21 +423,6 @@ namespace vcpkg::Dependencies { } - const PackageSpec& AnyAction::spec() const - { - if (const auto p = install_action.get()) - { - return p->spec; - } - - if (const auto p = remove_action.get()) - { - return p->spec; - } - - Checks::exit_with_message(VCPKG_LINE_INFO, "Null action"); - } - bool ExportPlanAction::compare_by_name(const ExportPlanAction* left, const ExportPlanAction* right) { return left->spec.name() < right->spec.name(); @@ -294,145 +470,6 @@ namespace vcpkg::Dependencies return left->spec.name() < right->spec.name(); } - MapPortFileProvider::MapPortFileProvider(const std::unordered_map<std::string, SourceControlFileLocation>& map) - : ports(map) - { - } - - Optional<const SourceControlFileLocation&> MapPortFileProvider::get_control_file(const std::string& spec) const - { - auto scf = ports.find(spec); - if (scf == ports.end()) return nullopt; - return scf->second; - } - - std::vector<const SourceControlFileLocation*> MapPortFileProvider::load_all_control_files() const - { - return Util::fmap(ports, [](auto&& kvpair) -> const SourceControlFileLocation* { return &kvpair.second; }); - } - - PathsPortFileProvider::PathsPortFileProvider(const vcpkg::VcpkgPaths& paths, - const std::vector<std::string>* ports_dirs_paths) - : filesystem(paths.get_filesystem()) - { - auto& fs = Files::get_real_filesystem(); - if (ports_dirs_paths) - { - for (auto&& overlay_path : *ports_dirs_paths) - { - if (!overlay_path.empty()) - { - auto overlay = fs::stdfs::canonical(fs::u8path(overlay_path)); - - Checks::check_exit(VCPKG_LINE_INFO, - filesystem.exists(overlay), - "Error: Path \"%s\" does not exist", - overlay.string()); - - Checks::check_exit(VCPKG_LINE_INFO, - fs::is_directory(fs.status(VCPKG_LINE_INFO, overlay)), - "Error: Path \"%s\" must be a directory", - overlay.string()); - - ports_dirs.emplace_back(overlay); - } - } - } - ports_dirs.emplace_back(paths.ports); - } - - Optional<const SourceControlFileLocation&> PathsPortFileProvider::get_control_file(const std::string& spec) const - { - auto cache_it = cache.find(spec); - if (cache_it != cache.end()) - { - return cache_it->second; - } - - for (auto&& ports_dir : ports_dirs) - { - // Try loading individual port - if (filesystem.exists(ports_dir / "CONTROL")) - { - auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); - if (auto scf = maybe_scf.get()) - { - if (scf->get()->core_paragraph->name == spec) - { - SourceControlFileLocation scfl{std::move(*scf), ports_dir}; - auto it = cache.emplace(spec, std::move(scfl)); - return it.first->second; - } - } - else - { - vcpkg::print_error_message(maybe_scf.error()); - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Failed to load port from %s", spec, ports_dir.u8string()); - } - } - - auto found_scf = Paragraphs::try_load_port(filesystem, ports_dir / spec); - if (auto scf = found_scf.get()) - { - if (scf->get()->core_paragraph->name == spec) - { - SourceControlFileLocation scfl{std::move(*scf), ports_dir / spec}; - auto it = cache.emplace(spec, std::move(scfl)); - return it.first->second; - } - } - } - - return nullopt; - } - - std::vector<const SourceControlFileLocation*> PathsPortFileProvider::load_all_control_files() const - { - // Reload cache with ports contained in all ports_dirs - cache.clear(); - std::vector<const SourceControlFileLocation*> ret; - for (auto&& ports_dir : ports_dirs) - { - // Try loading individual port - if (filesystem.exists(ports_dir / "CONTROL")) - { - auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); - if (auto scf = maybe_scf.get()) - { - auto port_name = scf->get()->core_paragraph->name; - if (cache.find(port_name) == cache.end()) - { - SourceControlFileLocation scfl{std::move(*scf), ports_dir}; - auto it = cache.emplace(port_name, std::move(scfl)); - ret.emplace_back(&it.first->second); - } - } - else - { - vcpkg::print_error_message(maybe_scf.error()); - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Failed to load port from %s", ports_dir.u8string()); - } - continue; - } - - // Try loading all ports inside ports_dir - auto found_scf = Paragraphs::load_all_ports(filesystem, ports_dir); - for (auto&& scf : found_scf) - { - auto port_name = scf->core_paragraph->name; - if (cache.find(port_name) == cache.end()) - { - SourceControlFileLocation scfl{std::move(scf), ports_dir / port_name}; - auto it = cache.emplace(port_name, std::move(scfl)); - ret.emplace_back(&it.first->second); - } - } - } - return ret; - } - std::vector<RemovePlanAction> create_remove_plan(const std::vector<PackageSpec>& specs, const StatusParagraphs& status_db) { @@ -488,7 +525,8 @@ namespace vcpkg::Dependencies auto installed_ports = get_installed_ports(status_db); const std::unordered_set<PackageSpec> specs_as_set(specs.cbegin(), specs.cend()); - return Graphs::topological_sort(specs, RemoveAdjacencyProvider{status_db, installed_ports, specs_as_set}, {}); + return Graphs::topological_sort( + std::move(specs), RemoveAdjacencyProvider{status_db, installed_ports, specs_as_set}, {}); } std::vector<ExportPlanAction> create_export_plan(const std::vector<PackageSpec>& specs, @@ -534,362 +572,333 @@ namespace vcpkg::Dependencies return toposort; } - enum class MarkPlusResult + void PackageGraph::mark_user_requested(const PackageSpec& spec) { - FEATURE_NOT_FOUND, - SUCCESS, - }; + m_graph->get(spec).request_type = RequestType::USER_REQUESTED; + } - static MarkPlusResult mark_plus(const std::string& feature, - Cluster& cluster, - ClusterGraph& graph, - GraphPlan& graph_plan, - const std::unordered_set<std::string>& prevent_default_features); - - static void mark_minus(Cluster& cluster, - ClusterGraph& graph, - GraphPlan& graph_plan, - const std::unordered_set<std::string>& prevent_default_features); - - static MarkPlusResult follow_plus_dependencies(const std::string& feature, - Cluster& cluster, - ClusterGraph& graph, - GraphPlan& graph_plan, - const std::unordered_set<std::string>& prevent_default_features) + ActionPlan create_feature_install_plan(const PortFileProvider::PortFileProvider& port_provider, + const CMakeVars::CMakeVarProvider& var_provider, + const std::vector<FullPackageSpec>& specs, + const StatusParagraphs& status_db, + const CreateInstallPlanOptions& options) { - if (auto p_source = cluster.source.get()) - { - auto it_build_edges = p_source->build_edges.find(feature); - if (it_build_edges != p_source->build_edges.end()) - { - // mark this package for rebuilding if needed - mark_minus(cluster, graph, graph_plan, prevent_default_features); + PackageGraph pgraph(port_provider, var_provider, status_db); - graph_plan.install_graph.add_vertex({&cluster}); - cluster.to_install_features.insert(feature); - - if (feature != "core") - { - // All features implicitly depend on core - auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); - - // Should be impossible for "core" to not exist - Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); - } - - if (!cluster.installed.get() && !Util::Sets::contains(prevent_default_features, cluster.spec.name())) - { - // Add the default features of this package if it was not previously installed and it isn't being - // suppressed. - auto res = mark_plus("", cluster, graph, graph_plan, prevent_default_features); - - Checks::check_exit(VCPKG_LINE_INFO, - res == MarkPlusResult::SUCCESS, - "Error: Unable to satisfy default dependencies of %s", - cluster.spec); - } + std::vector<FeatureSpec> feature_specs; + for (const FullPackageSpec& spec : specs) + { + const SourceControlFileLocation* scfl = port_provider.get_control_file(spec.package_spec.name()).get(); - for (auto&& depend : it_build_edges->second) - { - auto& depend_cluster = graph.get(depend.spec()); - auto res = mark_plus(depend.feature(), depend_cluster, graph, graph_plan, prevent_default_features); + Checks::check_exit( + VCPKG_LINE_INFO, scfl, "Error: Cannot find definition for package `%s`.", spec.package_spec.name()); - Checks::check_exit(VCPKG_LINE_INFO, - res == MarkPlusResult::SUCCESS, - "Error: Unable to satisfy dependency %s of %s", - depend, - FeatureSpec(cluster.spec, feature)); + const std::vector<std::string> all_features = + Util::fmap(scfl->source_control_file->feature_paragraphs, + [](auto&& feature_paragraph) { return feature_paragraph->name; }); - if (&depend_cluster == &cluster) continue; - graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); - } + auto fspecs = + spec.to_feature_specs(scfl->source_control_file->core_paragraph->default_features, all_features); + feature_specs.insert( + feature_specs.end(), std::make_move_iterator(fspecs.begin()), std::make_move_iterator(fspecs.end())); + } + Util::sort_unique_erase(feature_specs); - return MarkPlusResult::SUCCESS; - } + for (const FeatureSpec& spec : feature_specs) + { + pgraph.mark_user_requested(spec.spec()); } + pgraph.install(feature_specs); - // The feature was not available in the installed package nor the source paragraph. - return MarkPlusResult::FEATURE_NOT_FOUND; + return pgraph.serialize(options); } - MarkPlusResult mark_plus(const std::string& feature, - Cluster& cluster, - ClusterGraph& graph, - GraphPlan& graph_plan, - const std::unordered_set<std::string>& prevent_default_features) + void PackageGraph::mark_for_reinstall(const PackageSpec& first_remove_spec, + std::vector<FeatureSpec>& out_reinstall_requirements) { - auto& plus = cluster.plus[feature]; - if (plus) return MarkPlusResult::SUCCESS; - plus = true; + std::set<PackageSpec> removed; + std::vector<PackageSpec> to_remove{first_remove_spec}; - const auto p_source = cluster.source.get(); - if (p_source == nullptr) + while (!to_remove.empty()) { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Cannot find definition for package `%s`.", cluster.spec.name()); - } + PackageSpec remove_spec = std::move(to_remove.back()); + to_remove.pop_back(); - auto&& control_file = *p_source->scfl->source_control_file.get(); - if (feature.empty()) - { - // Add default features for this package. This is an exact reference, so ignore prevent_default_features. - for (auto&& default_feature : control_file.core_paragraph.get()->default_features) - { - auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) - { - return res; - } - } + if (!removed.insert(remove_spec).second) continue; - // "core" is always required. - return mark_plus("core", cluster, graph, graph_plan, prevent_default_features); - } + Cluster& clust = m_graph->get(remove_spec); + ClusterInstalled& info = clust.m_installed.value_or_exit(VCPKG_LINE_INFO); - if (feature == "*") - { - for (auto&& fpgh : control_file.feature_paragraphs) + if (!clust.m_install_info) { - auto res = mark_plus(fpgh->name, cluster, graph, graph_plan, prevent_default_features); - - Checks::check_exit(VCPKG_LINE_INFO, - res == MarkPlusResult::SUCCESS, - "Error: Internal error while installing feature %s in %s", - fpgh->name, - cluster.spec); + clust.create_install_info(out_reinstall_requirements); } - auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); - - Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); - return MarkPlusResult::SUCCESS; + to_remove.insert(to_remove.end(), info.remove_edges.begin(), info.remove_edges.end()); } - - if (auto p_installed = cluster.installed.get()) - { - if (p_installed->original_features.find(feature) != p_installed->original_features.end()) - { - return MarkPlusResult::SUCCESS; - } - } - - // The feature was not previously installed. Mark the cluster - // (aka the entire port) to be removed before re-adding it. - mark_minus(cluster, graph, graph_plan, prevent_default_features); - - return follow_plus_dependencies(feature, cluster, graph, graph_plan, prevent_default_features); } - void mark_minus(Cluster& cluster, - ClusterGraph& graph, - GraphPlan& graph_plan, - const std::unordered_set<std::string>& prevent_default_features) + /// The list of specs to install should already have default features expanded + void PackageGraph::install(Span<const FeatureSpec> specs) { - if (cluster.minus) return; - cluster.minus = true; - cluster.transient_uninstalled = true; - - auto p_installed = cluster.installed.get(); - auto p_source = cluster.source.get(); - - Checks::check_exit( - VCPKG_LINE_INFO, - p_source, - "Error: cannot locate new portfile for %s. Please explicitly remove this package with `vcpkg remove %s`.", - cluster.spec, - cluster.spec); + // We batch resolving qualified dependencies, because it's an invocation of CMake which + // takes ~150ms per call. + std::vector<FeatureSpec> qualified_dependencies; + std::vector<FeatureSpec> next_dependencies{specs.begin(), specs.end()}; - if (p_installed) + // Keep running while there is any chance of finding more dependencies + while (!next_dependencies.empty()) { - graph_plan.remove_graph.add_vertex({&cluster}); - for (auto&& edge : p_installed->remove_edges) + // Keep running until the only dependencies left are qualified + while (!next_dependencies.empty()) { - auto& depend_cluster = graph.get(edge); - Checks::check_exit(VCPKG_LINE_INFO, &cluster != &depend_cluster); - graph_plan.remove_graph.add_edge({&cluster}, {&depend_cluster}); - mark_minus(depend_cluster, graph, graph_plan, prevent_default_features); - } + // Extract the top of the stack + FeatureSpec spec = std::move(next_dependencies.back()); + next_dependencies.pop_back(); - // Reinstall all original features. Don't use mark_plus because it will ignore them since they are - // "already installed". - for (auto&& f : p_installed->original_features) - { - auto res = follow_plus_dependencies(f, cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) + // Get the cluster for the PackageSpec of the FeatureSpec we are adding to the install graph + Cluster& clust = m_graph->get(spec.spec()); + + // If this spec hasn't already had its qualified dependencies resolved + if (!m_var_provider.get_dep_info_vars(spec.spec()).has_value()) { - System::print2(System::Color::warning, - "Warning: could not reinstall feature ", - FeatureSpec{cluster.spec, f}, - "\n"); + // TODO: There's always the chance that we don't find the feature we're looking for (probably a + // malformed CONTROL file somewhere). We should probably output a better error. + const std::vector<Dependency>* paragraph_depends = nullptr; + if (spec.feature() == "core") + { + paragraph_depends = &clust.m_scfl.source_control_file->core_paragraph->depends; + } + else if (spec.feature() == "default") + { + } + else + { + auto maybe_paragraph = clust.m_scfl.source_control_file->find_feature(spec.feature()); + Checks::check_exit(VCPKG_LINE_INFO, + maybe_paragraph.has_value(), + "Package %s does not have a %s feature", + spec.name(), + spec.feature()); + paragraph_depends = &maybe_paragraph.value_or_exit(VCPKG_LINE_INFO).depends; + } + + // And it has at least one qualified dependency + if (paragraph_depends && Util::any_of(*paragraph_depends, + [](auto&& dep) { return !dep.qualifier.empty(); })) + { + // Add it to the next batch run + qualified_dependencies.emplace_back(spec); + } } - } - // Check if any default features have been added - auto& previous_df = p_installed->ipv.core->package.default_features; - auto&& control_file = *p_source->scfl->source_control_file.get(); - for (auto&& default_feature : control_file.core_paragraph->default_features) - { - if (std::find(previous_df.begin(), previous_df.end(), default_feature) == previous_df.end()) + if (clust.m_install_info.has_value()) { - // This is a new default feature, mark it for installation - auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) + clust.add_feature(spec.feature(), m_var_provider, next_dependencies); + } + else + { + if (!clust.m_installed.has_value()) + { + clust.create_install_info(next_dependencies); + clust.add_feature(spec.feature(), m_var_provider, next_dependencies); + } + else { - System::print2(System::Color::warning, - "Warning: could not install new default feature ", - FeatureSpec{cluster.spec, default_feature}, - "\n"); + if (spec.feature() == "default") + { + if (!clust.m_installed.get()->defaults_requested) + { + clust.m_installed.get()->defaults_requested = true; + if (!clust.has_defaults_installed()) + { + mark_for_reinstall(spec.spec(), next_dependencies); + } + } + } + else if (!clust.has_feature_installed(spec.feature())) + { + // If install_info is not present and it is already installed, we have never added a feature + // which hasn't already been installed to this cluster. In this case, we need to reinstall + // the port if the feature isn't already present. + mark_for_reinstall(spec.spec(), next_dependencies); + clust.add_feature(spec.feature(), m_var_provider, next_dependencies); + } } } } + + if (!qualified_dependencies.empty()) + { + Util::sort_unique_erase(qualified_dependencies); + + // Extract the package specs we need to get dependency info from. We don't run the triplet on a per + // feature basis. We run it once for the whole port. + auto qualified_package_specs = + Util::fmap(qualified_dependencies, [](const FeatureSpec& fspec) { return fspec.spec(); }); + Util::sort_unique_erase(qualified_package_specs); + m_var_provider.load_dep_info_vars(qualified_package_specs); + + // Put all the FeatureSpecs for which we had qualified dependencies back on the dependencies stack. + // We need to recheck if evaluating the triplet revealed any new dependencies. + next_dependencies.insert(next_dependencies.end(), + std::make_move_iterator(qualified_dependencies.begin()), + std::make_move_iterator(qualified_dependencies.end())); + qualified_dependencies.clear(); + } } } - std::vector<AnyAction> create_feature_install_plan(const PortFileProvider& provider, - const std::vector<FeatureSpec>& specs, - const StatusParagraphs& status_db, - const CreateInstallPlanOptions& options) + void PackageGraph::upgrade(Span<const PackageSpec> specs) { - std::unordered_set<std::string> prevent_default_features; - for (auto&& spec : specs) - { - // When "core" is explicitly listed, default features should not be installed. - if (spec.feature() == "core") prevent_default_features.insert(spec.name()); - } + std::vector<FeatureSpec> reinstall_reqs; - PackageGraph pgraph(provider, status_db); - for (auto&& spec : specs) - { - // If preventing default features, ignore the automatically generated "" references - if (spec.feature().empty() && Util::Sets::contains(prevent_default_features, spec.name())) continue; - pgraph.install(spec, prevent_default_features); - } + for (const PackageSpec& spec : specs) + mark_for_reinstall(spec, reinstall_reqs); - return pgraph.serialize(options); + Util::sort_unique_erase(reinstall_reqs); + + install(reinstall_reqs); } - /// <summary>Figure out which actions are required to install features specifications in `specs`.</summary> - /// <param name="map">Map of all source control files in the current environment.</param> - /// <param name="specs">Feature specifications to resolve dependencies for.</param> - /// <param name="status_db">Status of installed packages in the current environment.</param> - std::vector<AnyAction> create_feature_install_plan( - const std::unordered_map<std::string, SourceControlFileLocation>& map, - const std::vector<FeatureSpec>& specs, - const StatusParagraphs& status_db) + ActionPlan create_upgrade_plan(const PortFileProvider::PortFileProvider& port_provider, + const CMakeVars::CMakeVarProvider& var_provider, + const std::vector<PackageSpec>& specs, + const StatusParagraphs& status_db, + const CreateInstallPlanOptions& options) { - MapPortFileProvider provider(map); - return create_feature_install_plan(provider, specs, status_db); + PackageGraph pgraph(port_provider, var_provider, status_db); + + pgraph.upgrade(specs); + + return pgraph.serialize(options); } - /// <param name="prevent_default_features"> - /// List of package names for which default features should not be installed instead of the core package (e.g. if - /// the user is currently installing specific features of that package). - /// </param> - void PackageGraph::install(const FeatureSpec& spec, - const std::unordered_set<std::string>& prevent_default_features) const + ActionPlan PackageGraph::serialize(const CreateInstallPlanOptions& options) const { - Cluster& spec_cluster = m_graph->get(spec.spec()); - spec_cluster.request_type = RequestType::USER_REQUESTED; + struct BaseEdgeProvider : Graphs::AdjacencyProvider<PackageSpec, const Cluster*> + { + BaseEdgeProvider(const ClusterGraph& parent) : m_parent(parent) {} - auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); + std::string to_string(const PackageSpec& spec) const override { return spec.to_string(); } + const Cluster* load_vertex_data(const PackageSpec& spec) const override + { + return &m_parent.find_or_exit(spec, VCPKG_LINE_INFO); + } - Checks::check_exit(VCPKG_LINE_INFO, - res == MarkPlusResult::SUCCESS, - "Error: `%s` is not a feature of package `%s`", - spec.feature(), - spec.name()); + const ClusterGraph& m_parent; + }; - m_graph_plan->install_graph.add_vertex(ClusterPtr{&spec_cluster}); - } + struct RemoveEdgeProvider final : BaseEdgeProvider + { + using BaseEdgeProvider::BaseEdgeProvider; - void PackageGraph::upgrade(const PackageSpec& spec) const - { - Cluster& spec_cluster = m_graph->get(spec); - spec_cluster.request_type = RequestType::USER_REQUESTED; + std::vector<PackageSpec> adjacency_list(const Cluster* const& vertex) const override + { + auto&& set = vertex->m_installed.value_or_exit(VCPKG_LINE_INFO).remove_edges; + return {set.begin(), set.end()}; + } + } removeedgeprovider(*m_graph); - mark_minus(spec_cluster, *m_graph, *m_graph_plan, {}); - } + struct InstallEdgeProvider final : BaseEdgeProvider + { + using BaseEdgeProvider::BaseEdgeProvider; - std::vector<AnyAction> PackageGraph::serialize(const CreateInstallPlanOptions& options) const - { - auto remove_vertex_list = m_graph_plan->remove_graph.vertex_list(); - auto remove_toposort = - Graphs::topological_sort(remove_vertex_list, m_graph_plan->remove_graph, options.randomizer); + std::vector<PackageSpec> adjacency_list(const Cluster* const& vertex) const override + { + if (!vertex->m_install_info.has_value()) return {}; - auto insert_vertex_list = m_graph_plan->install_graph.vertex_list(); - auto insert_toposort = - Graphs::topological_sort(insert_vertex_list, m_graph_plan->install_graph, options.randomizer); + auto& info = vertex->m_install_info.value_or_exit(VCPKG_LINE_INFO); + std::vector<PackageSpec> deps; + for (auto&& kv : info.build_edges) + for (auto&& e : kv.second) + { + auto spec = e.spec(); + if (spec != vertex->m_spec) deps.push_back(std::move(spec)); + } + Util::sort_unique_erase(deps); + return deps; + } + } installedgeprovider(*m_graph); - std::vector<AnyAction> plan; + std::vector<PackageSpec> removed_vertices; + std::vector<PackageSpec> installed_vertices; + for (auto&& kv : *m_graph) + { + if (kv.second.m_install_info.has_value() && kv.second.m_installed.has_value()) + { + removed_vertices.push_back(kv.first); + } + if (kv.second.m_install_info.has_value() || kv.second.request_type == RequestType::USER_REQUESTED) + { + installed_vertices.push_back(kv.first); + } + } + auto remove_toposort = Graphs::topological_sort(removed_vertices, removeedgeprovider, options.randomizer); + auto insert_toposort = Graphs::topological_sort(installed_vertices, installedgeprovider, options.randomizer); + + ActionPlan plan; for (auto&& p_cluster : remove_toposort) { - plan.emplace_back(RemovePlanAction{ - std::move(p_cluster->spec), - RemovePlanType::REMOVE, - p_cluster->request_type, - }); + plan.remove_actions.emplace_back(p_cluster->m_spec, RemovePlanType::REMOVE, p_cluster->request_type); } for (auto&& p_cluster : insert_toposort) { - if (p_cluster->transient_uninstalled) + // Every cluster that has an install_info needs to be built + // If a cluster only has an installed object and is marked as user requested we should still report it. + if (auto info_ptr = p_cluster->m_install_info.get()) { - // If it will be transiently uninstalled, we need to issue a full installation command - auto* pscfl = p_cluster->source.value_or_exit(VCPKG_LINE_INFO).scfl; - Checks::check_exit( - VCPKG_LINE_INFO, pscfl != nullptr, "Error: Expected a SourceControlFileLocation to exist"); - auto&& scfl = *pscfl; - - auto dep_specs = Util::fmap(m_graph_plan->install_graph.adjacency_list(p_cluster), - [](ClusterPtr const& p) { return p->spec; }); - Util::sort_unique_erase(dep_specs); - - plan.emplace_back(InstallPlanAction{ - p_cluster->spec, - scfl, - p_cluster->to_install_features, - p_cluster->request_type, - std::move(dep_specs), - }); + auto&& scfl = p_cluster->m_scfl; + + std::unordered_map<std::string, std::vector<FeatureSpec>> computed_edges; + for (auto&& kv : info_ptr->build_edges) + { + std::set<FeatureSpec> fspecs; + for (auto&& fspec : kv.second) + { + if (fspec.feature() != "default") + { + fspecs.insert(fspec); + continue; + } + auto&& dep_clust = m_graph->get(fspec.spec()); + const auto& default_features = + [&]{ + if (dep_clust.m_install_info.has_value()) return dep_clust.m_scfl.source_control_file->core_paragraph->default_features; + if (auto p = dep_clust.m_installed.get()) return p->ipv.core->package.default_features; + Checks::unreachable(VCPKG_LINE_INFO); + }(); + for (auto&& default_feature : default_features) + fspecs.emplace(fspec.spec(), default_feature); + } + computed_edges[kv.first].assign(fspecs.begin(), fspecs.end()); + } + + plan.install_actions.emplace_back( + p_cluster->m_spec, scfl, p_cluster->request_type, std::move(computed_edges)); } - else + else if (p_cluster->request_type == RequestType::USER_REQUESTED && p_cluster->m_installed.has_value()) { - // 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; - auto&& installed = p_cluster->installed.value_or_exit(VCPKG_LINE_INFO); - plan.emplace_back(InstallPlanAction{ - InstalledPackageView{installed.ipv}, - installed.original_features, - p_cluster->request_type, - }); + auto&& installed = p_cluster->m_installed.value_or_exit(VCPKG_LINE_INFO); + plan.already_installed.emplace_back(Util::copy(installed.ipv), p_cluster->request_type); } } return plan; } - static std::unique_ptr<ClusterGraph> create_feature_install_graph(const PortFileProvider& map, - const StatusParagraphs& status_db) + static std::unique_ptr<ClusterGraph> create_feature_install_graph( + const PortFileProvider::PortFileProvider& port_provider, const StatusParagraphs& status_db) { - std::unique_ptr<ClusterGraph> graph = std::make_unique<ClusterGraph>(map); + std::unique_ptr<ClusterGraph> graph = std::make_unique<ClusterGraph>(port_provider); auto installed_ports = get_installed_ports(status_db); for (auto&& ipv : installed_ports) { - Cluster& cluster = graph->get(ipv.spec()); - - cluster.transient_uninstalled = false; - - cluster.installed = [](const InstalledPackageView& ipv) -> ClusterInstalled { - ClusterInstalled ret; - ret.ipv = ipv; - ret.original_features.emplace("core"); - for (auto&& feature : ipv.features) - ret.original_features.emplace(feature->package.feature); - return ret; - }(ipv); + graph->get(ipv); } // Populate the graph with "remove edges", which are the reverse of the Build-Depends edges. @@ -899,7 +908,7 @@ namespace vcpkg::Dependencies for (auto&& dep : deps) { - auto p_installed = graph->get(dep).installed.get(); + auto p_installed = graph->get(dep).m_installed.get(); Checks::check_exit(VCPKG_LINE_INFO, p_installed, "Error: database corrupted. Package %s is installed but dependency %s is not.", @@ -911,16 +920,16 @@ namespace vcpkg::Dependencies return graph; } - PackageGraph::PackageGraph(const PortFileProvider& provider, const StatusParagraphs& status_db) - : m_graph_plan(std::make_unique<GraphPlan>()), m_graph(create_feature_install_graph(provider, status_db)) + PackageGraph::PackageGraph(const PortFileProvider::PortFileProvider& port_provider, + const CMakeVars::CMakeVarProvider& var_provider, + const StatusParagraphs& status_db) + : m_var_provider(var_provider), m_graph(create_feature_install_graph(port_provider, status_db)) { } PackageGraph::~PackageGraph() = default; - void print_plan(const std::vector<AnyAction>& action_plan, - const bool is_recursive, - const fs::path& default_ports_dir) + void print_plan(const ActionPlan& action_plan, const bool is_recursive, const fs::path& default_ports_dir) { std::vector<const RemovePlanAction*> remove_plans; std::vector<const InstallPlanAction*> rebuilt_plans; @@ -929,44 +938,38 @@ namespace vcpkg::Dependencies std::vector<const InstallPlanAction*> already_installed_plans; std::vector<const InstallPlanAction*> excluded; - const bool has_non_user_requested_packages = Util::find_if(action_plan, [](const AnyAction& package) -> bool { - if (auto iplan = package.install_action.get()) - return iplan->request_type != RequestType::USER_REQUESTED; - else - return false; - }) != action_plan.cend(); + const bool has_non_user_requested_packages = + Util::find_if(action_plan.install_actions, [](const InstallPlanAction& action) -> bool { + return action.request_type != RequestType::USER_REQUESTED; + }) != action_plan.install_actions.cend(); - for (auto&& action : action_plan) + for (auto&& remove_action : action_plan.remove_actions) + { + remove_plans.emplace_back(&remove_action); + } + for (auto&& install_action : action_plan.install_actions) { - if (auto install_action = action.install_action.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()) { - // 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 - { - switch (install_action->plan_type) - { - case InstallPlanType::ALREADY_INSTALLED: - if (install_action->request_type == RequestType::USER_REQUESTED) - already_installed_plans.emplace_back(install_action); - break; - case InstallPlanType::BUILD_AND_INSTALL: new_plans.emplace_back(install_action); break; - case InstallPlanType::EXCLUDED: excluded.emplace_back(install_action); break; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } + rebuilt_plans.push_back(&install_action); } - else if (auto remove_action = action.remove_action.get()) + else { - remove_plans.emplace_back(remove_action); + if (install_action.plan_type == InstallPlanType::EXCLUDED) + excluded.push_back(&install_action); + else + new_plans.push_back(&install_action); } } + for (auto&& action : action_plan.already_installed) + { + if (action.request_type == RequestType::USER_REQUESTED) already_installed_plans.emplace_back(&action); + } + already_installed_plans = Util::fmap(action_plan.already_installed, [](auto&& action) { return &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); diff --git a/toolsrc/src/vcpkg/export.cpp b/toolsrc/src/vcpkg/export.cpp index 34f9053ea..0094b712c 100644 --- a/toolsrc/src/vcpkg/export.cpp +++ b/toolsrc/src/vcpkg/export.cpp @@ -2,9 +2,9 @@ #include <vcpkg/commands.h> #include <vcpkg/dependencies.h> +#include <vcpkg/export.chocolatey.h> #include <vcpkg/export.h> #include <vcpkg/export.ifw.h> -#include <vcpkg/export.chocolatey.h> #include <vcpkg/help.h> #include <vcpkg/input.h> #include <vcpkg/install.h> @@ -307,8 +307,10 @@ namespace vcpkg::Export {OPTION_IFW_REPOSITORY_DIR_PATH, "Specify the directory path for the exported repository"}, {OPTION_IFW_CONFIG_FILE_PATH, "Specify the temporary file path for the installer configuration"}, {OPTION_IFW_INSTALLER_FILE_PATH, "Specify the file path for the exported installer"}, - {OPTION_CHOCOLATEY_MAINTAINER, "Specify the maintainer for the exported Chocolatey package (experimental feature)"}, - {OPTION_CHOCOLATEY_VERSION_SUFFIX, "Specify the version suffix to add for the exported Chocolatey package (experimental feature)"}, + {OPTION_CHOCOLATEY_MAINTAINER, + "Specify the maintainer for the exported Chocolatey package (experimental feature)"}, + {OPTION_CHOCOLATEY_VERSION_SUFFIX, + "Specify the version suffix to add for the exported Chocolatey package (experimental feature)"}, }}; const CommandStructure COMMAND_STRUCTURE = { @@ -521,7 +523,7 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console const StatusParagraphs status_db = database_load_check(paths); // Load ports from ports dirs - Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); std::vector<ExportPlanAction> export_plan = Dependencies::create_export_plan(opts.specs, status_db); Checks::check_exit(VCPKG_LINE_INFO, !export_plan.empty(), "Export plan cannot be empty"); diff --git a/toolsrc/src/vcpkg/help.cpp b/toolsrc/src/vcpkg/help.cpp index a4908e02e..78bc43539 100644 --- a/toolsrc/src/vcpkg/help.cpp +++ b/toolsrc/src/vcpkg/help.cpp @@ -74,10 +74,36 @@ namespace vcpkg::Help void help_topic_valid_triplet(const VcpkgPaths& paths) { - System::print2("Available architecture triplets:\n"); - for (auto&& triplet : paths.get_available_triplets()) + std::map<std::string, std::vector<const VcpkgPaths::TripletFile*>> triplets_per_location; + vcpkg::Util::group_by(paths.get_available_triplets(), + &triplets_per_location, + [](const VcpkgPaths::TripletFile& triplet_file) -> std::string { + return triplet_file.location.u8string(); + }); + + System::print2("Available architecture triplets\n"); + + System::print2("VCPKG built-in triplets:\n"); + for (auto* triplet : triplets_per_location[paths.triplets.u8string()]) + { + System::print2(" ", triplet->name, '\n'); + } + triplets_per_location.erase(paths.triplets.u8string()); + + System::print2("\nVCPKG community triplets:\n"); + for (auto* triplet : triplets_per_location[paths.community_triplets.u8string()]) + { + System::print2(" ", triplet->name, '\n'); + } + triplets_per_location.erase(paths.community_triplets.u8string()); + + for (auto&& kv_pair : triplets_per_location) { - System::print2(" ", triplet, '\n'); + System::print2("\nOverlay triplets from ", kv_pair.first, ":\n"); + for (auto* triplet : kv_pair.second) + { + System::print2(" ", triplet->name, '\n'); + } } } diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index a082f1b95..0af9a33a1 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -5,6 +5,7 @@ #include <vcpkg/base/system.print.h> #include <vcpkg/base/util.h> #include <vcpkg/build.h> +#include <vcpkg/cmakevars.h> #include <vcpkg/commands.h> #include <vcpkg/dependencies.h> #include <vcpkg/globalstate.h> @@ -18,6 +19,7 @@ namespace vcpkg::Install { + using namespace vcpkg; using namespace Dependencies; using file_pack = std::pair<std::string, std::string>; @@ -295,8 +297,9 @@ namespace vcpkg::Install using Build::ExtendedBuildResult; ExtendedBuildResult perform_install_plan_action(const VcpkgPaths& paths, - const InstallPlanAction& action, - StatusParagraphs& status_db) + InstallPlanAction& action, + StatusParagraphs& status_db, + const CMakeVars::CMakeVarProvider& var_provider) { const InstallPlanType& plan_type = action.plan_type; const std::string display_name = action.spec.to_string(); @@ -338,8 +341,13 @@ namespace vcpkg::Install auto result = [&]() -> Build::ExtendedBuildResult { const auto& scfl = action.source_control_file_location.value_or_exit(VCPKG_LINE_INFO); - const Build::BuildPackageConfig build_config{ - scfl, action.spec.triplet(), action.build_options, action.feature_list}; + const Build::BuildPackageConfig build_config{scfl, + action.spec.triplet(), + action.build_options, + var_provider, + std::move(action.feature_dependencies), + std::move(action.package_dependencies), + std::move(action.feature_list)}; return Build::build_package(paths, build_config, status_db); }(); @@ -422,53 +430,60 @@ namespace vcpkg::Install } } - InstallSummary perform(const std::vector<AnyAction>& action_plan, + InstallSummary perform(ActionPlan& action_plan, const KeepGoing keep_going, const VcpkgPaths& paths, - StatusParagraphs& status_db) + StatusParagraphs& status_db, + const CMakeVars::CMakeVarProvider& var_provider) { std::vector<SpecSummary> results; const auto timer = Chrono::ElapsedTimer::create_started(); size_t counter = 0; - const size_t package_count = action_plan.size(); + const size_t package_count = action_plan.remove_actions.size() + action_plan.install_actions.size(); - for (const auto& action : action_plan) - { + auto with_tracking = [&](const PackageSpec& spec, auto f) { const auto build_timer = Chrono::ElapsedTimer::create_started(); counter++; - const PackageSpec& spec = action.spec(); const std::string display_name = spec.to_string(); System::printf("Starting package %zd/%zd: %s\n", counter, package_count, display_name); - results.emplace_back(spec, &action); + results.emplace_back(spec, nullptr); - if (const auto install_action = action.install_action.get()) - { - auto result = perform_install_plan_action(paths, *install_action, status_db); + f(); + + results.back().timing = build_timer.elapsed(); + System::printf("Elapsed time for package %s: %s\n", display_name, results.back().timing); + }; + + for (auto&& action : action_plan.remove_actions) + { + with_tracking(action.spec, + [&]() { Remove::perform_remove_plan_action(paths, action, Remove::Purge::YES, &status_db); }); + } + + for (auto&& action : action_plan.already_installed) + { + results.emplace_back(action.spec, &action); + results.back().build_result = perform_install_plan_action(paths, action, status_db, var_provider); + } + + for (auto&& action : action_plan.install_actions) + { + with_tracking(action.spec, [&]() { + auto result = perform_install_plan_action(paths, action, status_db, var_provider); if (result.code != BuildResult::SUCCEEDED && keep_going == KeepGoing::NO) { - System::print2(Build::create_user_troubleshooting_message(install_action->spec), '\n'); + System::print2(Build::create_user_troubleshooting_message(action.spec), '\n'); Checks::exit_fail(VCPKG_LINE_INFO); } + results.back().action = &action; results.back().build_result = std::move(result); - } - else if (const auto remove_action = action.remove_action.get()) - { - Remove::perform_remove_plan_action(paths, *remove_action, Remove::Purge::YES, &status_db); - } - else - { - Checks::unreachable(VCPKG_LINE_INFO); - } - - results.back().timing = build_timer.elapsed(); - System::printf("Elapsed time for package %s: %s\n", display_name, results.back().timing); + }); } - return InstallSummary{std::move(results), timer.to_string()}; } @@ -514,7 +529,8 @@ namespace vcpkg::Install static void print_cmake_information(const BinaryParagraph& bpgh, const VcpkgPaths& paths) { - static const std::regex cmake_library_regex(R"(\badd_library\(([^\s\)]+)\s)", std::regex_constants::ECMAScript); + static const std::regex cmake_library_regex(R"(\badd_library\(([^\$\s\)]+)\s)", + std::regex_constants::ECMAScript); auto& fs = paths.get_filesystem(); @@ -651,6 +667,7 @@ namespace vcpkg::Install auto& fs = paths.get_filesystem(); // create the plan + System::print2("Computing installation plan...\n"); StatusParagraphs status_db = database_load_check(paths); Build::DownloadTool download_tool = Build::DownloadTool::BUILT_IN; @@ -669,33 +686,39 @@ namespace vcpkg::Install }; //// Load ports from ports dirs - PathsPortFileProvider provider(paths, args.overlay_ports.get()); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + CMakeVars::TripletCMakeVarProvider var_provider(paths); // Note: action_plan will hold raw pointers to SourceControlFileLocations from this map - std::vector<AnyAction> action_plan = - create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db); + auto action_plan = Dependencies::create_feature_install_plan(provider, var_provider, specs, status_db); - for (auto&& action : action_plan) + std::vector<FullPackageSpec> install_package_specs; + for (auto&& action : action_plan.install_actions) { - if (auto p_install = action.install_action.get()) - { - p_install->build_options = install_plan_options; - if (p_install->request_type != RequestType::USER_REQUESTED) - p_install->build_options.use_head_version = Build::UseHeadVersion::NO; - } + action.build_options = install_plan_options; + if (action.request_type != RequestType::USER_REQUESTED) + action.build_options.use_head_version = Build::UseHeadVersion::NO; + + install_package_specs.emplace_back(FullPackageSpec{action.spec, action.feature_list}); } + var_provider.load_tag_vars(install_package_specs, provider); + // 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_action.get()) - return Hash::get_string_hash(iaction->spec.to_string(), Hash::Algorithm::Sha256); - else if (auto raction = action.remove_action.get()) - return "R$" + Hash::get_string_hash(raction->spec.to_string(), Hash::Algorithm::Sha256); - Checks::unreachable(VCPKG_LINE_INFO); - }); + std::string specs_string; + for (auto&& remove_action : action_plan.remove_actions) + { + if (!specs_string.empty()) specs_string += ","; + specs_string += "R$" + Hash::get_string_hash(remove_action.spec.to_string(), Hash::Algorithm::Sha256); + } + for (auto&& install_action : action_plan.install_actions) + { + if (!specs_string.empty()) specs_string += ","; + specs_string += Hash::get_string_hash(install_action.spec.to_string(), Hash::Algorithm::Sha256); + } Metrics::g_metrics.lock()->track_property("installplan_1", specs_string); @@ -706,7 +729,7 @@ namespace vcpkg::Install Checks::exit_success(VCPKG_LINE_INFO); } - const InstallSummary summary = perform(action_plan, keep_going, paths, status_db); + const InstallSummary summary = perform(action_plan, keep_going, paths, status_db, var_provider); System::print2("\nTotal elapsed time: ", summary.total_elapsed_time, "\n\n"); @@ -729,19 +752,16 @@ namespace vcpkg::Install for (auto&& result : summary.results) { if (!result.action) continue; - if (auto p_install_action = result.action->install_action.get()) - { - if (p_install_action->request_type != RequestType::USER_REQUESTED) continue; - auto bpgh = result.get_binary_paragraph(); - if (!bpgh) continue; - print_cmake_information(*bpgh, paths); - } + if (result.action->request_type != RequestType::USER_REQUESTED) continue; + auto bpgh = result.get_binary_paragraph(); + if (!bpgh) continue; + print_cmake_information(*bpgh, paths); } Checks::exit_success(VCPKG_LINE_INFO); } - SpecSummary::SpecSummary(const PackageSpec& spec, const Dependencies::AnyAction* action) + SpecSummary::SpecSummary(const PackageSpec& spec, const Dependencies::InstallPlanAction* action) : spec(spec), build_result{BuildResult::NULLVALUE, nullptr}, action(action) { } @@ -750,12 +770,9 @@ namespace vcpkg::Install { if (build_result.binary_control_file) return &build_result.binary_control_file->core_paragraph; if (action) - if (auto p_install_plan = action->install_action.get()) + if (auto p_status = action->installed_package.get()) { - if (auto p_status = p_install_plan->installed_package.get()) - { - return &p_status->core->package; - } + return &p_status->core->package; } return nullptr; } diff --git a/toolsrc/src/vcpkg/logicexpression.cpp b/toolsrc/src/vcpkg/logicexpression.cpp index ccb8b00c4..2f646e80a 100644 --- a/toolsrc/src/vcpkg/logicexpression.cpp +++ b/toolsrc/src/vcpkg/logicexpression.cpp @@ -1,7 +1,6 @@ - #include "pch.h" -#include <vcpkg/base/checks.h> +#include <vcpkg/base/strings.h> #include <vcpkg/base/system.print.h> #include <vcpkg/logicexpression.h> @@ -18,22 +17,37 @@ namespace vcpkg const std::string line; const std::string message; - void print_error() const + std::string format_error() const { - System::print2(System::Color::error, - "Error: ", - message, - "\n" - " on expression: \"", - line, - "\"\n", - " ", - std::string(column, ' '), - "^\n"); - Checks::exit_fail(VCPKG_LINE_INFO); + return Strings::concat("Error: ", + message, + "\n" + " on expression: \"", + line, + "\"\n", + " ", + std::string(column, ' '), + "^\n"); } }; + enum class Identifier + { + invalid, // not a recognized identifier + x64, + x86, + arm, + arm64, + + windows, + linux, + osx, + uwp, + android, + + static_link, + }; + // logic expression supports the following : // primary-expression: // ( logic-expression ) @@ -53,12 +67,33 @@ namespace vcpkg class ExpressionParser { public: - ExpressionParser(const std::string& str, const std::string& evaluation_context) + ExpressionParser(const std::string& str, const ExpressionContext& context) : raw_text(str) - , evaluation_context(evaluation_context) + , evaluation_context(context) , current_iter(raw_text.begin()) , current_char(get_current_char()) { + { + auto override_vars = evaluation_context.cmake_context.find("VCPKG_DEP_INFO_OVERRIDE_VARS"); + if (override_vars != evaluation_context.cmake_context.end()) + { + auto cmake_list = Strings::split(override_vars->second, ";"); + for (auto& override_id : cmake_list) + { + if (!override_id.empty()) + { + if (override_id[0] == '!') + { + context_override.insert({override_id.substr(1), false}); + } + else + { + context_override.insert({override_id, true}); + } + } + } + } + } skip_whitespace(); final_result = logic_expression(); @@ -67,21 +102,18 @@ namespace vcpkg { add_error("Invalid logic expression"); } - - if (err) - { - err->print_error(); - final_result = false; - } } bool get_result() const { return final_result; } - bool has_error() const { return err == nullptr; } + const ParseError* get_error() const { return err.get(); } private: const std::string& raw_text; - const std::string& evaluation_context; + + const ExpressionContext& evaluation_context; + std::map<std::string, bool> context_override; + std::string::const_iterator current_iter; char current_char; @@ -143,9 +175,106 @@ namespace vcpkg return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-'); } - bool evaluate_identifier(const std::string name) const + // Legacy evaluation only searches for substrings. Use this only for diagnostic purposes. + bool evaluate_identifier_legacy(const std::string name) const { - return evaluation_context.find(name) != std::string::npos; + return evaluation_context.legacy_context.find(name) != std::string::npos; + } + + static Identifier string2identifier(const std::string& name) + { + static const std::map<std::string, Identifier> id_map = { + {"x64", Identifier::x64}, + {"x86", Identifier::x86}, + {"arm", Identifier::arm}, + {"arm64", Identifier::arm64}, + {"windows", Identifier::windows}, + {"linux", Identifier::linux}, + {"osx", Identifier::osx}, + {"uwp", Identifier::uwp}, + {"android", Identifier::android}, + {"static", Identifier::static_link}, + }; + + auto id_pair = id_map.find(name); + + if (id_pair == id_map.end()) + { + return Identifier::invalid; + } + + return id_pair->second; + } + + bool true_if_exists_and_equal(const std::string& variable_name, const std::string& value) + { + auto iter = evaluation_context.cmake_context.find(variable_name); + if (iter == evaluation_context.cmake_context.end()) + { + return false; + } + return iter->second == value; + } + + // If an identifier is on the explicit override list, return the override value + // Otherwise fall back to the built in logic to evaluate + // All unrecognized identifiers are an error + bool evaluate_identifier_cmake(const std::string name, int column) + { + auto id = string2identifier(name); + + switch (id) + { + case Identifier::invalid: + // Point out in the diagnostic that they should add to the override list because that is what + // most users should do, however it is also valid to update the built in identifiers to recognize + // the name. + add_error("Unrecognized identifer name. Add to override list in triplet file.", column); + break; + + case Identifier::x64: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "x64"); + case Identifier::x86: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "x86"); + case Identifier::arm: + // For backwards compatability arm is also true for arm64. + // This is because it previously was only checking for a substring. + return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm") || + true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm64"); + case Identifier::arm64: return true_if_exists_and_equal("VCPKG_TARGET_ARCHITECTURE", "arm64"); + case Identifier::windows: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "") || true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"); + case Identifier::linux: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Linux"); + case Identifier::osx: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Darwin"); + case Identifier::uwp: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "WindowsStore"); + case Identifier::android: return true_if_exists_and_equal("VCPKG_CMAKE_SYSTEM_NAME", "Android"); + case Identifier::static_link: return true_if_exists_and_equal("VCPKG_LIBRARY_LINKAGE", "static"); + } + + return evaluation_context.legacy_context.find(name) != std::string::npos; + } + + bool evaluate_identifier(const std::string name, int column) + { + if (!context_override.empty()) + { + auto override_id = context_override.find(name); + if (override_id != context_override.end()) + { + return override_id->second; + } + // Fall through to use the cmake logic if the id does not have an override + } + + bool legacy = evaluate_identifier_legacy(name); + bool cmake = evaluate_identifier_cmake(name, column); + if (legacy != cmake) + { + // Legacy evaluation only used the name of the triplet, now we use the actual + // cmake variables. This has the potential to break custom triplets. + // For now just print a message, this will need to change once we start introducing + // new variables that did not exist previously (such as host-*) + System::print2( + "Warning: Identifier logic evaluation does not match legacy evaluation:\n ", name, '\n'); + } + return cmake; } // identifier: @@ -154,6 +283,7 @@ namespace vcpkg { auto curr = current(); std::string name; + auto starting_column = current_column(); for (curr = current(); is_alphanum(curr); curr = next()) { @@ -166,7 +296,7 @@ namespace vcpkg return false; } - bool result = evaluate_identifier(name); + bool result = evaluate_identifier(name, starting_column); skip_whitespace(); return result; } @@ -194,6 +324,7 @@ namespace vcpkg while (next() == oper) { }; + skip_whitespace(); seed = operation(not_expression(), seed); } while (current() == oper); @@ -242,7 +373,7 @@ namespace vcpkg bool result = logic_expression(); if (current() != ')') { - add_error("Error: missing closing )"); + add_error("missing closing )"); return result; } next_skip_whitespace(); @@ -253,9 +384,14 @@ namespace vcpkg } }; - bool evaluate_expression(const std::string& expression, const std::string& evaluation_context) + ExpectedT<bool, std::string> evaluate_expression(const std::string& expression, const ExpressionContext& context) { - ExpressionParser parser(expression, evaluation_context); + ExpressionParser parser(expression, context); + + if (auto err = parser.get_error()) + { + return err->format_error(); + } return parser.get_result(); } diff --git a/toolsrc/src/vcpkg/packagespec.cpp b/toolsrc/src/vcpkg/packagespec.cpp index 52edf4b6f..ea25f57e1 100644 --- a/toolsrc/src/vcpkg/packagespec.cpp +++ b/toolsrc/src/vcpkg/packagespec.cpp @@ -46,7 +46,7 @@ namespace vcpkg for (auto&& feature : spec->features) f_specs.push_back(FeatureSpec{pspec, feature}); - if (spec->features.empty()) f_specs.push_back(FeatureSpec{pspec, ""}); + if (spec->features.empty()) f_specs.push_back(FeatureSpec{pspec, "core"}); } else { @@ -59,16 +59,44 @@ namespace vcpkg return f_specs; } - std::vector<FeatureSpec> FullPackageSpec::to_feature_specs(const std::vector<FullPackageSpec>& specs) + std::vector<FeatureSpec> FullPackageSpec::to_feature_specs(const std::vector<std::string>& default_features, + const std::vector<std::string>& all_features) const { - std::vector<FeatureSpec> ret; - for (auto&& spec : specs) + std::vector<FeatureSpec> feature_specs; + + if (Util::find(features, "*") != features.end()) { - ret.emplace_back(spec.package_spec, ""); - for (auto&& feature : spec.features) - ret.emplace_back(spec.package_spec, feature); + feature_specs.emplace_back(package_spec, "core"); + for (const std::string& feature : all_features) + { + feature_specs.emplace_back(package_spec, feature); + } } - return ret; + else + { + bool core = false; + for (const std::string& feature : features) + { + feature_specs.emplace_back(package_spec, feature); + + if (!core) + { + core = feature == "core"; + } + } + + if (!core) + { + feature_specs.emplace_back(package_spec, "core"); + + for (const std::string& def : default_features) + { + feature_specs.emplace_back(package_spec, def); + } + } + } + + return feature_specs; } ExpectedT<FullPackageSpec, PackageSpecParseResult> FullPackageSpec::from_string(const std::string& spec_as_string, diff --git a/toolsrc/src/vcpkg/paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index 9cb7caff1..797d681cd 100644 --- a/toolsrc/src/vcpkg/paragraphs.cpp +++ b/toolsrc/src/vcpkg/paragraphs.cpp @@ -205,10 +205,11 @@ namespace vcpkg::Paragraphs ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path) { - Expected<std::vector<RawParagraph>> pghs = get_paragraphs(fs, path / "CONTROL"); + const auto path_to_control = path / "CONTROL"; + Expected<std::vector<RawParagraph>> pghs = get_paragraphs(fs, path_to_control); if (auto vector_pghs = pghs.get()) { - return SourceControlFile::parse_control_file(std::move(*vector_pghs)); + return SourceControlFile::parse_control_file(path_to_control, std::move(*vector_pghs)); } auto error_info = std::make_unique<ParseControlErrorInfo>(); error_info->name = path.filename().generic_u8string(); diff --git a/toolsrc/src/vcpkg/portfileprovider.cpp b/toolsrc/src/vcpkg/portfileprovider.cpp new file mode 100644 index 000000000..6540f2949 --- /dev/null +++ b/toolsrc/src/vcpkg/portfileprovider.cpp @@ -0,0 +1,151 @@ +#include <pch.h> + +#include <vcpkg/paragraphs.h> +#include <vcpkg/portfileprovider.h> +#include <vcpkg/sourceparagraph.h> + +namespace vcpkg::PortFileProvider +{ + MapPortFileProvider::MapPortFileProvider(const std::unordered_map<std::string, SourceControlFileLocation>& map) + : ports(map) + { + } + + Optional<const SourceControlFileLocation&> MapPortFileProvider::get_control_file(const std::string& spec) const + { + auto scf = ports.find(spec); + if (scf == ports.end()) return nullopt; + return scf->second; + } + + std::vector<const SourceControlFileLocation*> MapPortFileProvider::load_all_control_files() const + { + return Util::fmap(ports, [](auto&& kvpair) -> const SourceControlFileLocation* { return &kvpair.second; }); + } + + PathsPortFileProvider::PathsPortFileProvider(const vcpkg::VcpkgPaths& paths, + const std::vector<std::string>* ports_dirs_paths) + : filesystem(paths.get_filesystem()) + { + auto& fs = Files::get_real_filesystem(); + if (ports_dirs_paths) + { + for (auto&& overlay_path : *ports_dirs_paths) + { + if (!overlay_path.empty()) + { + auto overlay = fs::stdfs::canonical(fs::u8path(overlay_path)); + + Checks::check_exit(VCPKG_LINE_INFO, + filesystem.exists(overlay), + "Error: Path \"%s\" does not exist", + overlay.string()); + + Checks::check_exit(VCPKG_LINE_INFO, + fs::is_directory(fs.status(VCPKG_LINE_INFO, overlay)), + "Error: Path \"%s\" must be a directory", + overlay.string()); + + ports_dirs.emplace_back(overlay); + } + } + } + ports_dirs.emplace_back(paths.ports); + } + + Optional<const SourceControlFileLocation&> PathsPortFileProvider::get_control_file(const std::string& spec) const + { + auto cache_it = cache.find(spec); + if (cache_it != cache.end()) + { + return cache_it->second; + } + + for (auto&& ports_dir : ports_dirs) + { + // Try loading individual port + if (filesystem.exists(ports_dir / "CONTROL")) + { + auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); + if (auto scf = maybe_scf.get()) + { + if (scf->get()->core_paragraph->name == spec) + { + auto it = cache.emplace(std::piecewise_construct, + std::forward_as_tuple(spec), + std::forward_as_tuple(std::move(*scf), ports_dir)); + return it.first->second; + } + } + else + { + vcpkg::print_error_message(maybe_scf.error()); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Failed to load port from %s", spec, ports_dir.u8string()); + } + } + + auto found_scf = Paragraphs::try_load_port(filesystem, ports_dir / spec); + if (auto scf = found_scf.get()) + { + if (scf->get()->core_paragraph->name == spec) + { + auto it = cache.emplace(std::piecewise_construct, + std::forward_as_tuple(spec), + std::forward_as_tuple(std::move(*scf), ports_dir / spec)); + return it.first->second; + } + } + } + + return nullopt; + } + + std::vector<const SourceControlFileLocation*> PathsPortFileProvider::load_all_control_files() const + { + // Reload cache with ports contained in all ports_dirs + cache.clear(); + std::vector<const SourceControlFileLocation*> ret; + for (auto&& ports_dir : ports_dirs) + { + // Try loading individual port + if (filesystem.exists(ports_dir / "CONTROL")) + { + auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); + if (auto scf = maybe_scf.get()) + { + auto port_name = scf->get()->core_paragraph->name; + if (cache.find(port_name) == cache.end()) + { + auto it = cache.emplace(std::piecewise_construct, + std::forward_as_tuple(port_name), + std::forward_as_tuple(std::move(*scf), ports_dir)); + ret.emplace_back(&it.first->second); + } + } + else + { + vcpkg::print_error_message(maybe_scf.error()); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Failed to load port from %s", ports_dir.u8string()); + } + continue; + } + + // Try loading all ports inside ports_dir + auto found_scf = Paragraphs::load_all_ports(filesystem, ports_dir); + for (auto&& scf : found_scf) + { + auto port_name = scf->core_paragraph->name; + if (cache.find(port_name) == cache.end()) + { + auto it = cache.emplace(std::piecewise_construct, + std::forward_as_tuple(port_name), + std::forward_as_tuple(std::move(scf), ports_dir / port_name)); + ret.emplace_back(&it.first->second); + } + } + } + return ret; + } +} diff --git a/toolsrc/src/vcpkg/postbuildlint.cpp b/toolsrc/src/vcpkg/postbuildlint.cpp index 2a13d2786..b346e1721 100644 --- a/toolsrc/src/vcpkg/postbuildlint.cpp +++ b/toolsrc/src/vcpkg/postbuildlint.cpp @@ -98,6 +98,93 @@ namespace vcpkg::PostBuildLint return LintStatus::SUCCESS; } + static LintStatus check_for_restricted_include_files(const Files::Filesystem& fs, + const Build::BuildPolicies& policies, + const fs::path& package_dir) + { + if (policies.is_enabled(BuildPolicy::ALLOW_RESTRICTED_HEADERS)) + { + return LintStatus::SUCCESS; + } + + // These files are taken from the libc6-dev package on Ubuntu inside /usr/include/x86_64-linux-gnu/sys/ + static constexpr StringLiteral restricted_sys_filenames[] = { + "acct.h", "auxv.h", "bitypes.h", "cdefs.h", "debugreg.h", "dir.h", "elf.h", + "epoll.h", "errno.h", "eventfd.h", "fanotify.h", "fcntl.h", "file.h", "fsuid.h", + "gmon.h", "gmon_out.h", "inotify.h", "io.h", "ioctl.h", "ipc.h", "kd.h", + "klog.h", "mman.h", "mount.h", "msg.h", "mtio.h", "param.h", "pci.h", + "perm.h", "personality.h", "poll.h", "prctl.h", "procfs.h", "profil.h", "ptrace.h", + "queue.h", "quota.h", "random.h", "raw.h", "reboot.h", "reg.h", "resource.h", + "select.h", "sem.h", "sendfile.h", "shm.h", "signal.h", "signalfd.h", "socket.h", + "socketvar.h", "soundcard.h", "stat.h", "statfs.h", "statvfs.h", "stropts.h", "swap.h", + "syscall.h", "sysctl.h", "sysinfo.h", "syslog.h", "sysmacros.h", "termios.h", "time.h", + "timeb.h", "timerfd.h", "times.h", "timex.h", "ttychars.h", "ttydefaults.h", "types.h", + "ucontext.h", "uio.h", "un.h", "unistd.h", "user.h", "ustat.h", "utsname.h", + "vfs.h", "vlimit.h", "vm86.h", "vt.h", "vtimes.h", "wait.h", "xattr.h", + }; + // These files are taken from the libc6-dev package on Ubuntu inside the /usr/include/ folder + static constexpr StringLiteral restricted_crt_filenames[] = { + "_G_config.h", "aio.h", "aliases.h", "alloca.h", "ar.h", "argp.h", + "argz.h", "assert.h", "byteswap.h", "complex.h", "cpio.h", "crypt.h", + "ctype.h", "dirent.h", "dlfcn.h", "elf.h", "endian.h", "envz.h", + "err.h", "errno.h", "error.h", "execinfo.h", "fcntl.h", "features.h", + "fenv.h", "fmtmsg.h", "fnmatch.h", "fstab.h", "fts.h", "ftw.h", + "gconv.h", "getopt.h", "glob.h", "gnu-versions.h", "grp.h", "gshadow.h", + "iconv.h", "ifaddrs.h", "inttypes.h", "langinfo.h", "lastlog.h", "libgen.h", + "libintl.h", "libio.h", "limits.h", "link.h", "locale.h", "malloc.h", + "math.h", "mcheck.h", "memory.h", "mntent.h", "monetary.h", "mqueue.h", + "netash", "netdb.h", "nl_types.h", "nss.h", "obstack.h", "paths.h", + "poll.h", "printf.h", "proc_service.h", "pthread.h", "pty.h", "pwd.h", + "re_comp.h", "regex.h", "regexp.h", "resolv.h", "sched.h", "search.h", + "semaphore.h", "setjmp.h", "sgtty.h", "shadow.h", "signal.h", "spawn.h", + "stab.h", "stdc-predef.h", "stdint.h", "stdio.h", "stdio_ext.h", "stdlib.h", + "string.h", "strings.h", "stropts.h", "syscall.h", "sysexits.h", "syslog.h", + "tar.h", "termio.h", "termios.h", "tgmath.h", "thread_db.h", "time.h", + "ttyent.h", "uchar.h", "ucontext.h", "ulimit.h", "unistd.h", "ustat.h", + "utime.h", "utmp.h", "utmpx.h", "values.h", "wait.h", "wchar.h", + "wctype.h", "wordexp.h", + }; + // These files are general names that have shown to be problematic in the past + static constexpr StringLiteral restricted_general_filenames[] = { + "json.h", + "parser.h", + "lexer.h", + "config.h", + "local.h", + "slice.h", + }; + static constexpr Span<const StringLiteral> restricted_lists[] = { + restricted_sys_filenames, restricted_crt_filenames, restricted_general_filenames}; + const fs::path include_dir = package_dir / "include"; + auto files = fs.get_files_non_recursive(include_dir); + auto filenames_v = Util::fmap(files, [](const auto& file) { return file.filename().u8string(); }); + std::set<std::string> filenames_s(filenames_v.begin(), filenames_v.end()); + + std::vector<fs::path> violations; + for (auto&& flist : restricted_lists) + for (auto&& f : flist) + { + if (Util::Sets::contains(filenames_s, f)) + { + violations.push_back(fs::u8path("include") / fs::u8path(f.c_str())); + } + } + + if (!violations.empty()) + { + System::print2(System::Color::warning, + "Restricted headers paths are present. These files can prevent the core C++ runtime and " + "other packages from compiling correctly:\n"); + Files::print_paths(violations); + System::print2("In exceptional circumstances, this policy can be disabled via ", + Build::to_cmake_variable(BuildPolicy::ALLOW_RESTRICTED_HEADERS), + "\n"); + return LintStatus::ERROR_DETECTED; + } + + return LintStatus::SUCCESS; + } + static LintStatus check_for_files_in_debug_include_directory(const Files::Filesystem& fs, const fs::path& package_dir) { @@ -142,10 +229,10 @@ namespace vcpkg::PostBuildLint const fs::path lib_cmake = package_dir / "lib" / "cmake"; if (fs.exists(lib_cmake)) { - System::printf( - System::Color::warning, - "The /lib/cmake folder should be merged with /debug/lib/cmake and moved to /share/%s/cmake.\n", - spec.name()); + System::printf(System::Color::warning, + "The /lib/cmake folder should be merged with /debug/lib/cmake and moved to " + "/share/%s/cmake.\nPlease use the helper function `vcpkg_fixup_cmake_targets()`\n", + spec.name()); return LintStatus::ERROR_DETECTED; } @@ -295,10 +382,12 @@ namespace vcpkg::PostBuildLint return LintStatus::SUCCESS; } - static LintStatus check_exports_of_dlls(const Build::BuildPolicies& policies, const std::vector<fs::path>& dlls, const fs::path& dumpbin_exe) + static LintStatus check_exports_of_dlls(const Build::BuildPolicies& policies, + const std::vector<fs::path>& dlls, + const fs::path& dumpbin_exe) { if (policies.is_enabled(BuildPolicy::DLLS_WITHOUT_EXPORTS)) return LintStatus::SUCCESS; - + std::vector<fs::path> dlls_with_no_exports; for (const fs::path& dll : dlls) { @@ -571,7 +660,7 @@ namespace vcpkg::PostBuildLint R"(If the creation of bin\ and/or debug\bin\ cannot be disabled, use this in the portfile to remove them)" "\n" "\n" - R"###( if(VCPKG_LIBRARY_LINKAGE STREQUAL static))###" + R"###( if(VCPKG_LIBRARY_LINKAGE STREQUAL "static"))###" "\n" R"###( file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin))###" "\n" @@ -760,6 +849,7 @@ namespace vcpkg::PostBuildLint } error_count += check_for_files_in_include_directory(fs, build_info.policies, package_dir); + error_count += check_for_restricted_include_files(fs, build_info.policies, package_dir); error_count += check_for_files_in_debug_include_directory(fs, package_dir); error_count += check_for_files_in_debug_share_directory(fs, package_dir); error_count += check_folder_lib_cmake(fs, package_dir, spec); diff --git a/toolsrc/src/vcpkg/remove.cpp b/toolsrc/src/vcpkg/remove.cpp index 65e00668a..1bcfe58a0 100644 --- a/toolsrc/src/vcpkg/remove.cpp +++ b/toolsrc/src/vcpkg/remove.cpp @@ -229,7 +229,7 @@ namespace vcpkg::Remove } // Load ports from ports dirs - Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); specs = Util::fmap(Update::find_outdated_packages(provider, status_db), [](auto&& outdated) { return outdated.spec; }); diff --git a/toolsrc/src/vcpkg/sourceparagraph.cpp b/toolsrc/src/vcpkg/sourceparagraph.cpp index ebb9cd4f4..26681e107 100644 --- a/toolsrc/src/vcpkg/sourceparagraph.cpp +++ b/toolsrc/src/vcpkg/sourceparagraph.cpp @@ -23,10 +23,10 @@ namespace vcpkg static const std::string FEATURE = "Feature"; static const std::string MAINTAINER = "Maintainer"; static const std::string SOURCE = "Source"; - static const std::string SUPPORTS = "Supports"; static const std::string VERSION = "Version"; static const std::string HOMEPAGE = "Homepage"; static const std::string TYPE = "Type"; + static const std::string SUPPORTS = "Supports"; } static Span<const std::string> get_list_of_valid_fields() @@ -39,6 +39,7 @@ namespace vcpkg SourceParagraphFields::BUILD_DEPENDS, SourceParagraphFields::HOMEPAGE, SourceParagraphFields::TYPE, + SourceParagraphFields::SUPPORTS, }; return valid_fields; @@ -101,7 +102,24 @@ namespace vcpkg } } - static ParseExpected<SourceParagraph> parse_source_paragraph(RawParagraph&& fields) + std::string Type::to_string(const Type& t) + { + switch (t.type) + { + case Type::ALIAS: return "Alias"; + case Type::PORT: return "Port"; + default: return "Unknown"; + } + } + + Type Type::from_string(const std::string& t) + { + if (t == "Alias") return Type{Type::ALIAS}; + if (t == "Port" || t == "") return Type{Type::PORT}; + return Type{Type::UNKNOWN}; + } + + static ParseExpected<SourceParagraph> parse_source_paragraph(const fs::path& path_to_control, RawParagraph&& fields) { ParagraphParser parser(std::move(fields)); @@ -115,17 +133,18 @@ namespace vcpkg spgh->homepage = parser.optional_field(SourceParagraphFields::HOMEPAGE); spgh->depends = expand_qualified_dependencies( parse_comma_list(parser.optional_field(SourceParagraphFields::BUILD_DEPENDS))); - spgh->supports = parse_comma_list(parser.optional_field(SourceParagraphFields::SUPPORTS)); spgh->default_features = parse_comma_list(parser.optional_field(SourceParagraphFields::DEFAULTFEATURES)); - - auto err = parser.error_info(spgh->name); + spgh->supports_expression = parser.optional_field(SourceParagraphFields::SUPPORTS); + spgh->type = Type::from_string(parser.optional_field(SourceParagraphFields::TYPE)); + auto err = parser.error_info(spgh->name.empty() ? path_to_control.u8string() : spgh->name); if (err) - return std::move(err); + return err; else - return std::move(spgh); + return spgh; } - static ParseExpected<FeatureParagraph> parse_feature_paragraph(RawParagraph&& fields) + static ParseExpected<FeatureParagraph> parse_feature_paragraph(const fs::path& path_to_control, + RawParagraph&& fields) { ParagraphParser parser(std::move(fields)); @@ -137,24 +156,26 @@ namespace vcpkg fpgh->depends = expand_qualified_dependencies( parse_comma_list(parser.optional_field(SourceParagraphFields::BUILD_DEPENDS))); - auto err = parser.error_info(fpgh->name); + auto err = parser.error_info(fpgh->name.empty() ? path_to_control.u8string() : fpgh->name); if (err) - return std::move(err); + return err; else - return std::move(fpgh); + return fpgh; } ParseExpected<SourceControlFile> SourceControlFile::parse_control_file( - std::vector<Parse::RawParagraph>&& control_paragraphs) + const fs::path& path_to_control, std::vector<Parse::RawParagraph>&& control_paragraphs) { if (control_paragraphs.size() == 0) { - return std::make_unique<Parse::ParseControlErrorInfo>(); + auto ret = std::make_unique<Parse::ParseControlErrorInfo>(); + ret->name = path_to_control.u8string(); + return ret; } auto control_file = std::make_unique<SourceControlFile>(); - auto maybe_source = parse_source_paragraph(std::move(control_paragraphs.front())); + auto maybe_source = parse_source_paragraph(path_to_control, std::move(control_paragraphs.front())); if (const auto source = maybe_source.get()) control_file->core_paragraph = std::move(*source); else @@ -164,14 +185,14 @@ namespace vcpkg for (auto&& feature_pgh : control_paragraphs) { - auto maybe_feature = parse_feature_paragraph(std::move(feature_pgh)); + auto maybe_feature = parse_feature_paragraph(path_to_control, std::move(feature_pgh)); if (const auto feature = maybe_feature.get()) control_file->feature_paragraphs.emplace_back(std::move(*feature)); else return std::move(maybe_feature).error(); } - return std::move(control_file); + return control_file; } Optional<const FeatureParagraph&> SourceControlFile::find_feature(const std::string& featurename) const @@ -183,6 +204,18 @@ namespace vcpkg else return nullopt; } + Optional<const std::vector<Dependency>&> SourceControlFile::find_dependencies_for_feature( + const std::string& featurename) const + { + if (featurename == "core") + { + return core_paragraph->depends; + } + else if (auto p_feature = find_feature(featurename).get()) + return p_feature->depends; + else + return nullopt; + } Dependency Dependency::parse_dependency(std::string name, std::string qualifier) { @@ -207,96 +240,62 @@ namespace vcpkg std::vector<Dependency> 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 Dependency::parse_dependency(depend_string, ""); - // expect of the form "\w+ \[\w+\]" - if (depend_string.c_str()[pos + 1] != '(' || depend_string[depend_string.size() - 1] != ')') + // First, try to find beginning and end of features list + auto end_of_features = depend_string.find(']'); + if (end_of_features != std::string::npos) { - // Error, but for now just slurp the entire string. - return Dependency::parse_dependency(depend_string, ""); + ++end_of_features; + } + else + { + end_of_features = depend_string.find(' '); + if (end_of_features == std::string::npos) end_of_features = depend_string.size(); } - return Dependency::parse_dependency(depend_string.substr(0, pos), - depend_string.substr(pos + 2, depend_string.size() - pos - 3)); - }); - } - std::vector<std::string> filter_dependencies(const std::vector<vcpkg::Dependency>& deps, const Triplet& t) - { - std::vector<std::string> ret; - for (auto&& dep : deps) - { - const auto& qualifier = dep.qualifier; - if (qualifier.empty() || evaluate_expression(qualifier, t.canonical_name())) + auto begin_of_qualifier = depend_string.find('(', end_of_features); + if (begin_of_qualifier == std::string::npos) { - ret.emplace_back(dep.name()); + return Dependency::parse_dependency(depend_string.substr(0, end_of_features), ""); } - } - return ret; + else + { + int depth = 1; + auto i = begin_of_qualifier + 1; + for (; i != depend_string.size(); ++i) + { + auto ch = depend_string[i]; + if (ch == '(') + ++depth; + else if (ch == ')') + --depth; + + if (depth == 0) break; + } + return Dependency::parse_dependency( + depend_string.substr(0, end_of_features), + depend_string.substr(begin_of_qualifier + 1, i - begin_of_qualifier - 1)); + } + }); } - std::vector<Features> filter_dependencies_to_features(const std::vector<vcpkg::Dependency>& deps, const Triplet& t) + std::vector<FullPackageSpec> filter_dependencies(const std::vector<vcpkg::Dependency>& deps, + const Triplet& t, + const std::unordered_map<std::string, std::string>& cmake_vars) { - std::vector<Features> ret; + std::vector<FullPackageSpec> ret; for (auto&& dep : deps) { const auto& qualifier = dep.qualifier; - if (qualifier.empty() || evaluate_expression(qualifier, t.canonical_name())) + if (qualifier.empty() || + evaluate_expression(qualifier, {cmake_vars, t.canonical_name()}).value_or_exit(VCPKG_LINE_INFO)) { - ret.emplace_back(dep.depend); + ret.emplace_back(FullPackageSpec( + PackageSpec::from_name_and_triplet(dep.depend.name, t).value_or_exit(VCPKG_LINE_INFO), + dep.depend.features)); } } return ret; } - 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); - } - std::string to_string(const Dependency& dep) { return dep.name(); } - - ExpectedT<Supports, std::vector<std::string>> Supports::parse(const std::vector<std::string>& strs) - { - Supports ret; - std::vector<std::string> unrecognized; - - for (auto&& str : strs) - { - if (str == "x64") - ret.architectures.push_back(Architecture::X64); - else if (str == "x86") - ret.architectures.push_back(Architecture::X86); - else if (str == "arm") - ret.architectures.push_back(Architecture::ARM); - else if (str == "windows") - ret.platforms.push_back(Platform::WINDOWS); - else if (str == "uwp") - ret.platforms.push_back(Platform::UWP); - else if (str == "v140") - ret.toolsets.push_back(ToolsetVersion::V140); - else if (str == "v141") - ret.toolsets.push_back(ToolsetVersion::V141); - else if (str == "crt-static") - ret.crt_linkages.push_back(Linkage::STATIC); - else if (str == "crt-dynamic") - ret.crt_linkages.push_back(Linkage::DYNAMIC); - else - unrecognized.push_back(str); - } - - if (unrecognized.empty()) - return std::move(ret); - else - return std::move(unrecognized); - } - - bool Supports::is_supported(Architecture arch, Platform plat, Linkage crt, ToolsetVersion tools) - { - const auto is_in_or_empty = [](auto v, auto&& c) -> bool { return c.empty() || c.end() != Util::find(c, v); }; - if (!is_in_or_empty(arch, architectures)) return false; - if (!is_in_or_empty(plat, platforms)) return false; - if (!is_in_or_empty(crt, crt_linkages)) return false; - if (!is_in_or_empty(tools, toolsets)) return false; - return true; - } } diff --git a/toolsrc/src/vcpkg/statusparagraph.cpp b/toolsrc/src/vcpkg/statusparagraph.cpp index f7e00f21c..c0ecca3a0 100644 --- a/toolsrc/src/vcpkg/statusparagraph.cpp +++ b/toolsrc/src/vcpkg/statusparagraph.cpp @@ -85,6 +85,34 @@ namespace vcpkg default: return "error"; } } + + std::unordered_map<std::string, std::vector<FeatureSpec>> InstalledPackageView::feature_dependencies() const + { + auto extract_deps = [&](const std::string& dep) { + FullPackageSpec dependency = + FullPackageSpec::from_string(dep, spec().triplet()).value_or_exit(VCPKG_LINE_INFO); + std::vector<FeatureSpec> fspecs; + + for (std::string& feature : dependency.features) + { + fspecs.emplace_back(dependency.package_spec, std::move(feature)); + } + + return fspecs; + }; + + std::unordered_map<std::string, std::vector<FeatureSpec>> deps; + + for (const StatusParagraph* const& feature : features) + { + deps.emplace(feature->package.feature, Util::fmap_flatten(feature->package.depends, extract_deps)); + } + + deps.emplace("core", Util::fmap_flatten(core->package.depends, extract_deps)); + + return deps; + } + std::vector<PackageSpec> InstalledPackageView::dependencies() const { // accumulate all features in installed dependencies diff --git a/toolsrc/src/vcpkg/statusparagraphs.cpp b/toolsrc/src/vcpkg/statusparagraphs.cpp index 3c81728bb..2fa9df7df 100644 --- a/toolsrc/src/vcpkg/statusparagraphs.cpp +++ b/toolsrc/src/vcpkg/statusparagraphs.cpp @@ -46,7 +46,7 @@ namespace vcpkg } } if (ipv.core != nullptr) - return std::move(ipv); + return ipv; else return nullopt; } diff --git a/toolsrc/src/vcpkg/update.cpp b/toolsrc/src/vcpkg/update.cpp index 6320bae5b..8cb2ac557 100644 --- a/toolsrc/src/vcpkg/update.cpp +++ b/toolsrc/src/vcpkg/update.cpp @@ -14,7 +14,7 @@ namespace vcpkg::Update return left.spec.name() < right.spec.name(); } - std::vector<OutdatedPackage> find_outdated_packages(const Dependencies::PortFileProvider& provider, + std::vector<OutdatedPackage> find_outdated_packages(const PortFileProvider::PortFileProvider& provider, const StatusParagraphs& status_db) { auto installed_packages = get_installed_ports(status_db); @@ -57,7 +57,7 @@ namespace vcpkg::Update const StatusParagraphs status_db = database_load_check(paths); - Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + PortFileProvider::PathsPortFileProvider provider(paths, args.overlay_ports.get()); const auto outdated_packages = SortedVector<OutdatedPackage>(find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 078121fcc..692cf1634 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -67,6 +67,7 @@ namespace vcpkg paths.ports = paths.root / "ports"; paths.installed = paths.root / "installed"; paths.triplets = paths.root / "triplets"; + paths.community_triplets = paths.triplets / "community"; if (auto scripts_dir = vcpkg_scripts_root_dir.get()) { @@ -110,7 +111,8 @@ namespace vcpkg paths.triplets_dirs.emplace_back(fs::stdfs::canonical(path)); } } - paths.triplets_dirs.emplace_back(fs::stdfs::canonical(paths.root / "triplets")); + paths.triplets_dirs.emplace_back(fs::stdfs::canonical(paths.triplets)); + paths.triplets_dirs.emplace_back(fs::stdfs::canonical(paths.community_triplets)); return paths; } @@ -130,23 +132,32 @@ namespace vcpkg bool VcpkgPaths::is_valid_triplet(const Triplet& t) const { const auto it = Util::find_if(this->get_available_triplets(), [&](auto&& available_triplet) { - return t.canonical_name() == available_triplet; + return t.canonical_name() == available_triplet.name; }); return it != this->get_available_triplets().cend(); } - const std::vector<std::string>& VcpkgPaths::get_available_triplets() const + const std::vector<std::string> VcpkgPaths::get_available_triplets_names() const { - return this->available_triplets.get_lazy([this]() -> std::vector<std::string> { - std::vector<std::string> output; + return vcpkg::Util::fmap(this->get_available_triplets(), + [](auto&& triplet_file) -> std::string { return triplet_file.name; }); + } + + const std::vector<VcpkgPaths::TripletFile>& VcpkgPaths::get_available_triplets() const + { + return this->available_triplets.get_lazy([this]() -> std::vector<TripletFile> { + std::vector<TripletFile> output; + Files::Filesystem& fs = this->get_filesystem(); for (auto&& triplets_dir : triplets_dirs) { - for (auto&& path : this->get_filesystem().get_files_non_recursive(triplets_dir)) + for (auto&& path : fs.get_files_non_recursive(triplets_dir)) { - output.push_back(path.stem().filename().string()); + if (fs::is_regular_file(fs.status(VCPKG_LINE_INFO, path))) + { + output.emplace_back(TripletFile(path.stem().filename().u8string(), triplets_dir)); + } } } - Util::sort_unique_erase(output); return output; }); } |
