diff options
| author | yurybura <yurybura@gmail.com> | 2018-05-11 13:34:43 +0300 |
|---|---|---|
| committer | yurybura <yurybura@gmail.com> | 2018-05-11 13:34:43 +0300 |
| commit | 7d261fbbc39a1d36027299190920e0a7e222ebd7 (patch) | |
| tree | d9b6745f2e6c670836cbbf61dbd2c4eb9ef857fe /toolsrc/src | |
| parent | 50e5ee1e40380cf543ae804775462181984a86dc (diff) | |
| parent | 9535a5631ac212b1c657a02be3ed9398df30c96c (diff) | |
| download | vcpkg-7d261fbbc39a1d36027299190920e0a7e222ebd7.tar.gz vcpkg-7d261fbbc39a1d36027299190920e0a7e222ebd7.zip | |
Merge branch 'master' of https://github.com/yurybura/vcpkg
Diffstat (limited to 'toolsrc/src')
46 files changed, 2358 insertions, 1420 deletions
diff --git a/toolsrc/src/tests.paragraph.cpp b/toolsrc/src/tests.paragraph.cpp index dca89bc59..9a56ad9ee 100644 --- a/toolsrc/src/tests.paragraph.cpp +++ b/toolsrc/src/tests.paragraph.cpp @@ -5,15 +5,6 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace Microsoft::VisualStudio::CppUnitTestFramework -{ - template<> - inline std::wstring ToString<vcpkg::PackageSpecParseResult>(const vcpkg::PackageSpecParseResult& t) - { - return ToString(static_cast<uint32_t>(t)); - } -} - namespace Strings = vcpkg::Strings; namespace UnitTest1 diff --git a/toolsrc/src/tests.plan.cpp b/toolsrc/src/tests.plan.cpp index 95056810c..238aa7032 100644 --- a/toolsrc/src/tests.plan.cpp +++ b/toolsrc/src/tests.plan.cpp @@ -81,7 +81,7 @@ namespace UnitTest1 { std::unordered_map<std::string, SourceControlFile> map; Triplet triplet; - PackageSpecMap(const Triplet& t) { triplet = t; } + PackageSpecMap(const Triplet& t = Triplet::X86_WINDOWS) noexcept { triplet = t; } PackageSpec emplace(const char* name, const char* depends = "", @@ -105,7 +105,7 @@ namespace UnitTest1 { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "b"); auto spec_b = spec_map.emplace("b", "c"); auto spec_c = spec_map.emplace("c"); @@ -124,7 +124,7 @@ namespace UnitTest1 { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "d"); auto spec_b = spec_map.emplace("b", "d, e"); auto spec_c = spec_map.emplace("c", "e, h"); @@ -167,7 +167,7 @@ namespace UnitTest1 std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; status_paragraphs.push_back(make_status_pgh("a")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a")}; auto install_plan = @@ -187,7 +187,7 @@ namespace UnitTest1 { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")}; auto spec_b = FullPackageSpec{spec_map.emplace("b")}; @@ -216,7 +216,7 @@ namespace UnitTest1 status_paragraphs.push_back(make_status_pgh("j", "k")); status_paragraphs.push_back(make_status_pgh("k")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "b, c, d, e, f, g, h, j, k"); auto spec_b = spec_map.emplace("b", "c, d, e, f, g, h, j, k"); @@ -251,7 +251,7 @@ namespace UnitTest1 status_paragraphs.push_back(make_status_pgh("b")); status_paragraphs.push_back(make_status_feature_pgh("b", "b1")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; 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", ""}})}; @@ -271,7 +271,7 @@ namespace UnitTest1 { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; 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", ""}})}; @@ -291,7 +291,7 @@ namespace UnitTest1 std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; status_paragraphs.push_back(make_status_pgh("a")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; auto spec_b = FullPackageSpec{spec_map.emplace("b")}; @@ -315,7 +315,7 @@ namespace UnitTest1 status_paragraphs.push_back(make_status_pgh("a")); status_paragraphs.push_back(make_status_feature_pgh("a", "a1", "")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}})}; auto spec_b = FullPackageSpec{spec_map.emplace("b")}; @@ -334,7 +334,7 @@ namespace UnitTest1 { std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a", "", {{"a1", "b[b1]"}, {"a2", "b[b2]"}, {"a3", "a[a2]"}}), {"a3"}}; @@ -355,7 +355,7 @@ namespace UnitTest1 std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; status_paragraphs.push_back(make_status_pgh("b")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[core]"), {"core"}}; auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; @@ -376,7 +376,7 @@ namespace UnitTest1 status_paragraphs.push_back(make_status_pgh("x", "b")); status_paragraphs.push_back(make_status_pgh("b")); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = FullPackageSpec{spec_map.emplace("a")}; auto spec_x = FullPackageSpec{spec_map.emplace("x", "a"), {"core"}}; @@ -593,17 +593,116 @@ namespace UnitTest1 features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); } + TEST_METHOD(install_plan_action_dependencies) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_c = spec_map.emplace("c"); + auto spec_b = spec_map.emplace("b", "c"); + spec_map.emplace("a", "b"); + + // 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))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"core"}, Triplet::X64_WINDOWS); + + features_check(&install_plan[1], "b", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[1].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_c}); + + features_check(&install_plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[2].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_b}); + } + + TEST_METHOD(install_plan_action_dependencies_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_c = spec_map.emplace("c"); + auto spec_b = spec_map.emplace("b", "c"); + spec_map.emplace("a", "c, b"); + + // 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))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"core"}, Triplet::X64_WINDOWS); + + features_check(&install_plan[1], "b", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[1].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_c}); + + features_check(&install_plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[2].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_b, spec_c}); + } + + TEST_METHOD(install_plan_action_dependencies_3) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"0", ""}, {"1", "a[0]"}}, {"1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"1", "0", "core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[0].install_action.get()->computed_dependencies == std::vector<PackageSpec>{}); + } + + TEST_METHOD(install_with_default_features) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + 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); + + Assert::IsTrue(install_plan.size() == 3); + remove_plan_check(&install_plan[0], "a"); + features_check(&install_plan[1], "b", {"core"}); + features_check(&install_plan[2], "a", {"0", "core"}); + } + TEST_METHOD(upgrade_with_default_features_1) { std::vector<std::unique_ptr<StatusParagraph>> pghs; pghs.push_back(make_status_pgh("a", "", "1")); pghs.push_back(make_status_feature_pgh("a", "0")); - pghs.back()->package.spec = - PackageSpec::from_name_and_triplet("a", Triplet::X86_WINDOWS).value_or_exit(VCPKG_LINE_INFO); StatusParagraphs status_db(std::move(pghs)); // Add a port "a" of which "core" and "0" are already installed. - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); Dependencies::MapPortFileProvider provider(spec_map.map); @@ -616,24 +715,47 @@ namespace UnitTest1 Assert::AreEqual(size_t(2), plan.size()); Assert::AreEqual("a", plan[0].spec().name().c_str()); - Assert::IsTrue(plan[0].remove_action.has_value()); - - Assert::AreEqual("a", plan[1].spec().name().c_str()); - Assert::IsTrue(plan[1].install_action.has_value()); - features_check(&plan[1], "a", {"core", "0"}, Triplet::X86_WINDOWS); + remove_plan_check(&plan[0], "a"); + features_check(&plan[1], "a", {"core", "0"}); } TEST_METHOD(upgrade_with_default_features_2) { std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("b")); - pghs.push_back(make_status_pgh("a", "b[core]")); - pghs.back()->package.spec = - PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + // B is currently installed _without_ default feature b0 + pghs.push_back(make_status_pgh("b", "", "b0", "x64-windows")); + pghs.push_back(make_status_pgh("a", "b[core]", "", "x64-windows")); + + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + 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(); + + // The upgrade should install the new default feature b1 but not b0 + Assert::AreEqual(size_t(4), plan.size()); + remove_plan_check(&plan[0], "a", Triplet::X64_WINDOWS); + remove_plan_check(&plan[1], "b", Triplet::X64_WINDOWS); + features_check(&plan[2], "b", {"core", "b1"}, Triplet::X64_WINDOWS); + features_check(&plan[3], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(upgrade_with_default_features_3) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + // note: unrelated package due to x86 triplet + pghs.push_back(make_status_pgh("b", "", "", "x86-windows")); + pghs.push_back(make_status_pgh("a", "", "", "x64-windows")); StatusParagraphs status_db(std::move(pghs)); - // Add a port "a" of which "core" and "0" are already installed. PackageSpecMap spec_map(Triplet::X64_WINDOWS); auto spec_a = spec_map.emplace("a", "b[core]"); spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"}); @@ -644,19 +766,33 @@ namespace UnitTest1 graph.upgrade(spec_a); auto plan = graph.serialize(); - // The upgrade should not install the default feature + // The upgrade should install the default feature Assert::AreEqual(size_t(3), plan.size()); + remove_plan_check(&plan[0], "a", Triplet::X64_WINDOWS); + features_check(&plan[1], "b", {"b0", "core"}, Triplet::X64_WINDOWS); + features_check(&plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + } - Assert::AreEqual("a", plan[0].spec().name().c_str()); - Assert::IsTrue(plan[0].remove_action.has_value()); + TEST_METHOD(upgrade_with_new_default_feature) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "", "0", "x86-windows")); - Assert::AreEqual("b", plan[1].spec().name().c_str()); - Assert::IsTrue(plan[1].install_action.has_value()); - features_check(&plan[1], "b", {"b0", "core"}, Triplet::X64_WINDOWS); + StatusParagraphs status_db(std::move(pghs)); - Assert::AreEqual("a", plan[2].spec().name().c_str()); - Assert::IsTrue(plan[2].install_action.has_value()); - features_check(&plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + 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(); + + // The upgrade should install the new default feature but not the old default feature 0 + Assert::AreEqual(size_t(2), plan.size()); + remove_plan_check(&plan[0], "a", Triplet::X86_WINDOWS); + features_check(&plan[1], "a", {"core", "1"}, Triplet::X86_WINDOWS); } TEST_METHOD(transitive_features_test) @@ -846,7 +982,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("a")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); Dependencies::MapPortFileProvider provider(spec_map.map); @@ -870,7 +1006,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("b", "a")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); spec_map.emplace("b", "a"); @@ -902,7 +1038,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("b")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); spec_map.emplace("b", "a"); @@ -926,7 +1062,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("a")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "b"); spec_map.emplace("b"); @@ -953,7 +1089,7 @@ namespace UnitTest1 pghs.push_back(make_status_feature_pgh("a", "a1")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); Dependencies::MapPortFileProvider provider(spec_map.map); @@ -979,7 +1115,7 @@ namespace UnitTest1 StatusParagraphs status_db(std::move(pghs)); // a1 was added as a default feature and should be installed in upgrade - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"}); Dependencies::MapPortFileProvider provider(spec_map.map); @@ -1005,7 +1141,7 @@ namespace UnitTest1 pghs.push_back(make_status_feature_pgh("a", "a2", "a[a1]")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}, {"a2", "a[a1]"}}); Dependencies::MapPortFileProvider provider(spec_map.map); @@ -1034,7 +1170,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("a")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); auto plan = Dependencies::create_export_plan({spec_a}, status_db); @@ -1051,7 +1187,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("b", "a")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); auto spec_b = spec_map.emplace("b", "a"); @@ -1072,7 +1208,7 @@ namespace UnitTest1 pghs.push_back(make_status_pgh("b")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); auto spec_b = spec_map.emplace("b", "a"); @@ -1087,7 +1223,7 @@ namespace UnitTest1 { StatusParagraphs status_db; - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a"); auto plan = Dependencies::create_export_plan({spec_a}, status_db); @@ -1105,7 +1241,7 @@ namespace UnitTest1 pghs.push_back(make_status_feature_pgh("a", "a1", "b[core]")); StatusParagraphs status_db(std::move(pghs)); - PackageSpecMap spec_map(Triplet::X86_WINDOWS); + PackageSpecMap spec_map; auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); auto plan = Dependencies::create_export_plan({spec_a}, status_db); diff --git a/toolsrc/src/tests.utils.cpp b/toolsrc/src/tests.utils.cpp index a3d8ffc7d..ac391f559 100644 --- a/toolsrc/src/tests.utils.cpp +++ b/toolsrc/src/tests.utils.cpp @@ -5,36 +5,6 @@ using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace vcpkg; -namespace Microsoft::VisualStudio::CppUnitTestFramework -{ - std::wstring ToString(const vcpkg::Dependencies::InstallPlanType& t) - { - switch (t) - { - case Dependencies::InstallPlanType::ALREADY_INSTALLED: return L"ALREADY_INSTALLED"; - case Dependencies::InstallPlanType::BUILD_AND_INSTALL: return L"BUILD_AND_INSTALL"; - case Dependencies::InstallPlanType::EXCLUDED: return L"EXCLUDED"; - case Dependencies::InstallPlanType::UNKNOWN: return L"UNKNOWN"; - default: return ToString(static_cast<int>(t)); - } - } - - std::wstring ToString(const vcpkg::Dependencies::RequestType& t) - { - switch (t) - { - case Dependencies::RequestType::AUTO_SELECTED: return L"AUTO_SELECTED"; - case Dependencies::RequestType::USER_REQUESTED: return L"USER_REQUESTED"; - case Dependencies::RequestType::UNKNOWN: return L"UNKNOWN"; - default: return ToString(static_cast<int>(t)); - } - } - - std::wstring ToString(const vcpkg::PackageSpecParseResult& t) { return ToString(static_cast<uint32_t>(t)); } - - std::wstring ToString(const vcpkg::PackageSpec& t) { return ToString(t.to_string()); } -} - std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, const char* depends, const char* default_features, diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index d9b915367..ac2eec876 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -70,7 +70,7 @@ static void inner(const VcpkgCmdArguments& args) fs::path vcpkg_root_dir; if (args.vcpkg_root_dir != nullptr) { - vcpkg_root_dir = fs::stdfs::absolute(Strings::to_utf16(*args.vcpkg_root_dir)); + vcpkg_root_dir = fs::stdfs::absolute(fs::u8path(*args.vcpkg_root_dir)); } else { @@ -94,6 +94,8 @@ static void inner(const VcpkgCmdArguments& args) Checks::check_exit(VCPKG_LINE_INFO, !vcpkg_root_dir.empty(), "Error: Could not detect vcpkg-root."); + Debug::println("Using vcpkg-root: %s", vcpkg_root_dir.u8string()); + auto default_vs_path = System::get_environment_variable("VCPKG_DEFAULT_VS_PATH").value_or(""); const Expected<VcpkgPaths> expected_paths = VcpkgPaths::create(vcpkg_root_dir, default_vs_path); @@ -157,7 +159,15 @@ static void inner(const VcpkgCmdArguments& args) } else { +#if defined(_WIN32) default_triplet = Triplet::X86_WINDOWS; +#elif defined(__APPLE__) + default_triplet = Triplet::from_canonical_name("x64-osx"); +#elif defined(__FreeBSD__) + default_triplet = Triplet::from_canonical_name("x64-freebsd"); +#else + default_triplet = Triplet::from_canonical_name("x64-linux"); +#endif } } @@ -215,6 +225,7 @@ static void load_config() } } +#if defined(_WIN32) static std::string trim_path_from_command_line(const std::string& full_command_line) { Checks::check_exit( @@ -235,6 +246,7 @@ static std::string trim_path_from_command_line(const std::string& full_command_l ++it; return std::string(it, full_command_line.cend()); } +#endif #if defined(_WIN32) int wmain(const int argc, const wchar_t* const* const argv) diff --git a/toolsrc/src/vcpkg/base/chrono.cpp b/toolsrc/src/vcpkg/base/chrono.cpp index 00f8ba3f1..2a76f5df0 100644 --- a/toolsrc/src/vcpkg/base/chrono.cpp +++ b/toolsrc/src/vcpkg/base/chrono.cpp @@ -15,7 +15,7 @@ namespace vcpkg::Chrono using std::chrono::nanoseconds; using std::chrono::seconds; - const double nanos_as_double = static_cast<double>(nanos.count()); + const auto nanos_as_double = static_cast<double>(nanos.count()); if (duration_cast<hours>(nanos) > hours()) { @@ -92,7 +92,7 @@ namespace vcpkg::Chrono Optional<CTime> CTime::parse(CStringView str) { CTime ret; - auto assigned = + const auto assigned = #if defined(_WIN32) sscanf_s #else @@ -117,15 +117,13 @@ namespace vcpkg::Chrono std::string CTime::to_string() const { - std::array<char, 80> date; - date.fill(0); - + std::array<char, 80> date{}; strftime(&date[0], date.size(), "%Y-%m-%dT%H:%M:%S.0Z", &m_tm); return &date[0]; } std::chrono::system_clock::time_point CTime::to_time_point() const { - auto t = mktime(&m_tm); + const time_t t = mktime(&m_tm); return std::chrono::system_clock::from_time_t(t); } } diff --git a/toolsrc/src/vcpkg/base/cofffilereader.cpp b/toolsrc/src/vcpkg/base/cofffilereader.cpp index 96d280108..2c09e2c19 100644 --- a/toolsrc/src/vcpkg/base/cofffilereader.cpp +++ b/toolsrc/src/vcpkg/base/cofffilereader.cpp @@ -2,6 +2,7 @@ #include <vcpkg/base/checks.h> #include <vcpkg/base/cofffilereader.h> +#include <vcpkg/base/stringliteral.h> using namespace std; @@ -43,21 +44,21 @@ namespace vcpkg::CoffFileReader actual); } - static void read_and_verify_PE_signature(fstream& fs) + static void read_and_verify_pe_signature(fstream& fs) { - static const size_t OFFSET_TO_PE_SIGNATURE_OFFSET = 0x3c; + static constexpr size_t OFFSET_TO_PE_SIGNATURE_OFFSET = 0x3c; - static const char* PE_SIGNATURE = "PE\0\0"; - static const size_t PE_SIGNATURE_SIZE = 4; + static constexpr StringLiteral PE_SIGNATURE = "PE\0\0"; + static constexpr size_t PE_SIGNATURE_SIZE = 4; fs.seekg(OFFSET_TO_PE_SIGNATURE_OFFSET, ios_base::beg); - const int32_t offset_to_PE_signature = read_value_from_stream<int32_t>(fs); + const auto offset_to_pe_signature = read_value_from_stream<int32_t>(fs); - fs.seekg(offset_to_PE_signature); + fs.seekg(offset_to_pe_signature); char signature[PE_SIGNATURE_SIZE]; fs.read(signature, PE_SIGNATURE_SIZE); - verify_equal_strings(VCPKG_LINE_INFO, PE_SIGNATURE, signature, PE_SIGNATURE_SIZE, "PE_SIGNATURE"); - fs.seekg(offset_to_PE_signature + PE_SIGNATURE_SIZE, ios_base::beg); + verify_equal_strings(VCPKG_LINE_INFO, PE_SIGNATURE.c_str(), signature, PE_SIGNATURE_SIZE, "PE_SIGNATURE"); + fs.seekg(offset_to_pe_signature + PE_SIGNATURE_SIZE, ios_base::beg); } static fpos_t align_to_size(const uint64_t unaligned, const uint64_t alignment_size) @@ -71,7 +72,7 @@ namespace vcpkg::CoffFileReader struct CoffFileHeader { - static const size_t HEADER_SIZE = 20; + static constexpr size_t HEADER_SIZE = 20; static CoffFileHeader read(fstream& fs) { @@ -83,11 +84,11 @@ namespace vcpkg::CoffFileReader MachineType machine_type() const { - static const size_t MACHINE_TYPE_OFFSET = 0; - static const size_t MACHINE_TYPE_SIZE = 2; + static constexpr size_t MACHINE_TYPE_OFFSET = 0; + static constexpr size_t MACHINE_TYPE_SIZE = 2; std::string machine_field_as_string = data.substr(MACHINE_TYPE_OFFSET, MACHINE_TYPE_SIZE); - const uint16_t machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); + const auto machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); return to_machine_type(machine); } @@ -97,13 +98,13 @@ namespace vcpkg::CoffFileReader struct ArchiveMemberHeader { - static const size_t HEADER_SIZE = 60; + static constexpr size_t HEADER_SIZE = 60; static ArchiveMemberHeader read(fstream& fs) { - static const size_t HEADER_END_OFFSET = 58; - static const char* HEADER_END = "`\n"; - static const size_t HEADER_END_SIZE = 2; + static constexpr size_t HEADER_END_OFFSET = 58; + static constexpr StringLiteral HEADER_END = "`\n"; + static constexpr size_t HEADER_END_SIZE = 2; ArchiveMemberHeader ret; ret.data.resize(HEADER_SIZE); @@ -113,7 +114,7 @@ namespace vcpkg::CoffFileReader { const std::string header_end = ret.data.substr(HEADER_END_OFFSET, HEADER_END_SIZE); verify_equal_strings( - VCPKG_LINE_INFO, HEADER_END, header_end.c_str(), HEADER_END_SIZE, "LIB HEADER_END"); + VCPKG_LINE_INFO, HEADER_END.c_str(), header_end.c_str(), HEADER_END_SIZE, "LIB HEADER_END"); } return ret; @@ -121,17 +122,17 @@ namespace vcpkg::CoffFileReader std::string name() const { - static const size_t HEADER_NAME_OFFSET = 0; - static const size_t HEADER_NAME_SIZE = 16; + static constexpr size_t HEADER_NAME_OFFSET = 0; + static constexpr size_t HEADER_NAME_SIZE = 16; return data.substr(HEADER_NAME_OFFSET, HEADER_NAME_SIZE); } uint64_t member_size() const { - static const size_t ALIGNMENT_SIZE = 2; + static constexpr size_t ALIGNMENT_SIZE = 2; - static const size_t HEADER_SIZE_OFFSET = 48; - static const size_t HEADER_SIZE_FIELD_SIZE = 10; + static constexpr size_t HEADER_SIZE_OFFSET = 48; + static constexpr size_t HEADER_SIZE_FIELD_SIZE = 10; const std::string as_string = data.substr(HEADER_SIZE_OFFSET, HEADER_SIZE_FIELD_SIZE); // This is in ASCII decimal representation const uint64_t value = std::strtoull(as_string.c_str(), nullptr, 10); @@ -147,18 +148,19 @@ namespace vcpkg::CoffFileReader { static OffsetsArray read(fstream& fs, const uint32_t offset_count) { - static const size_t OFFSET_WIDTH = 4; + static constexpr uint32_t OFFSET_WIDTH = 4; std::string raw_offsets; - const size_t raw_offset_size = offset_count * OFFSET_WIDTH; + const uint32_t raw_offset_size = offset_count * OFFSET_WIDTH; raw_offsets.resize(raw_offset_size); fs.read(&raw_offsets[0], raw_offset_size); OffsetsArray ret; for (uint32_t i = 0; i < offset_count; ++i) { - const std::string value_as_string = raw_offsets.substr(OFFSET_WIDTH * i, OFFSET_WIDTH * (i + 1)); - const uint32_t value = reinterpret_bytes<uint32_t>(value_as_string.c_str()); + const std::string value_as_string = raw_offsets.substr(OFFSET_WIDTH * static_cast<size_t>(i), + OFFSET_WIDTH * (static_cast<size_t>(i) + 1)); + const auto value = reinterpret_bytes<uint32_t>(value_as_string.c_str()); // Ignore offsets that point to offset 0. See vcpkg github #223 #288 #292 if (value != 0) @@ -177,28 +179,28 @@ namespace vcpkg::CoffFileReader struct ImportHeader { - static const size_t HEADER_SIZE = 20; + static constexpr size_t HEADER_SIZE = 20; static ImportHeader read(fstream& fs) { - static const size_t SIG1_OFFSET = 0; - static const uint16_t SIG1 = static_cast<uint16_t>(MachineType::UNKNOWN); - static const size_t SIG1_SIZE = 2; + static constexpr size_t SIG1_OFFSET = 0; + static constexpr auto SIG1 = static_cast<uint16_t>(MachineType::UNKNOWN); + static constexpr size_t SIG1_SIZE = 2; - static const size_t SIG2_OFFSET = 2; - static const uint16_t SIG2 = 0xFFFF; - static const size_t SIG2_SIZE = 2; + static constexpr size_t SIG2_OFFSET = 2; + static constexpr uint16_t SIG2 = 0xFFFF; + static constexpr size_t SIG2_SIZE = 2; ImportHeader ret; ret.data.resize(HEADER_SIZE); fs.read(&ret.data[0], HEADER_SIZE); const std::string sig1_as_string = ret.data.substr(SIG1_OFFSET, SIG1_SIZE); - const uint16_t sig1 = reinterpret_bytes<uint16_t>(sig1_as_string.c_str()); + const auto sig1 = reinterpret_bytes<uint16_t>(sig1_as_string.c_str()); Checks::check_exit(VCPKG_LINE_INFO, sig1 == SIG1, "Sig1 was incorrect. Expected %s but got %s", SIG1, sig1); const std::string sig2_as_string = ret.data.substr(SIG2_OFFSET, SIG2_SIZE); - const uint16_t sig2 = reinterpret_bytes<uint16_t>(sig2_as_string.c_str()); + const auto sig2 = reinterpret_bytes<uint16_t>(sig2_as_string.c_str()); Checks::check_exit(VCPKG_LINE_INFO, sig2 == SIG2, "Sig2 was incorrect. Expected %s but got %s", SIG2, sig2); return ret; @@ -206,11 +208,11 @@ namespace vcpkg::CoffFileReader MachineType machine_type() const { - static const size_t MACHINE_TYPE_OFFSET = 6; - static const size_t MACHINE_TYPE_SIZE = 2; + static constexpr size_t MACHINE_TYPE_OFFSET = 6; + static constexpr size_t MACHINE_TYPE_SIZE = 2; std::string machine_field_as_string = data.substr(MACHINE_TYPE_OFFSET, MACHINE_TYPE_SIZE); - const uint16_t machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); + const auto machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); return to_machine_type(machine); } @@ -220,14 +222,14 @@ namespace vcpkg::CoffFileReader static void read_and_verify_archive_file_signature(fstream& fs) { - static const char* FILE_START = "!<arch>\n"; - static const size_t FILE_START_SIZE = 8; + static constexpr StringLiteral FILE_START = "!<arch>\n"; + static constexpr size_t FILE_START_SIZE = 8; - fs.seekg(fs.beg); + fs.seekg(std::fstream::beg); char file_start[FILE_START_SIZE]; fs.read(file_start, FILE_START_SIZE); - verify_equal_strings(VCPKG_LINE_INFO, FILE_START, file_start, FILE_START_SIZE, "LIB FILE_START"); + verify_equal_strings(VCPKG_LINE_INFO, FILE_START.c_str(), file_start, FILE_START_SIZE, "LIB FILE_START"); } DllInfo read_dll(const fs::path& path) @@ -235,7 +237,7 @@ namespace vcpkg::CoffFileReader std::fstream fs(path, std::ios::in | std::ios::binary | std::ios::ate); Checks::check_exit(VCPKG_LINE_INFO, fs.is_open(), "Could not open file %s for reading", path.generic_string()); - read_and_verify_PE_signature(fs); + read_and_verify_pe_signature(fs); CoffFileHeader header = CoffFileHeader::read(fs); const MachineType machine = header.machine_type(); return {machine}; @@ -245,7 +247,7 @@ namespace vcpkg::CoffFileReader { void set_to_offset(const fpos_t position) { this->m_absolute_position = position; } - void set_to_current_pos(fstream& fs) { this->m_absolute_position = fs.tellg().seekpos(); } + void set_to_current_pos(fstream& fs) { this->m_absolute_position = fs.tellg(); } void seek_to_marker(fstream& fs) const { fs.seekg(this->m_absolute_position, ios_base::beg); } @@ -278,13 +280,13 @@ namespace vcpkg::CoffFileReader second_linker_member_header.name().substr(0, 2) == "/ ", "Could not find proper second linker member"); // The first 4 bytes contains the number of archive members - const uint32_t archive_member_count = read_value_from_stream<uint32_t>(fs); + const auto archive_member_count = read_value_from_stream<uint32_t>(fs); const OffsetsArray offsets = OffsetsArray::read(fs, archive_member_count); marker.advance_by(ArchiveMemberHeader::HEADER_SIZE + second_linker_member_header.member_size()); marker.seek_to_marker(fs); - const bool hasLongnameMemberHeader = peek_value_from_stream<uint16_t>(fs) == 0x2F2F; - if (hasLongnameMemberHeader) + const bool has_longname_member_header = peek_value_from_stream<uint16_t>(fs) == 0x2F2F; + if (has_longname_member_header) { const ArchiveMemberHeader longnames_member_header = ArchiveMemberHeader::read(fs); marker.advance_by(ArchiveMemberHeader::HEADER_SIZE + longnames_member_header.member_size()); @@ -297,10 +299,10 @@ namespace vcpkg::CoffFileReader { marker.set_to_offset(offset + ArchiveMemberHeader::HEADER_SIZE); // Skip the header, no need to read it. marker.seek_to_marker(fs); - const uint16_t first_two_bytes = peek_value_from_stream<uint16_t>(fs); - const bool isImportHeader = to_machine_type(first_two_bytes) == MachineType::UNKNOWN; + const auto first_two_bytes = peek_value_from_stream<uint16_t>(fs); + const bool is_import_header = to_machine_type(first_two_bytes) == MachineType::UNKNOWN; const MachineType machine = - isImportHeader ? ImportHeader::read(fs).machine_type() : CoffFileHeader::read(fs).machine_type(); + is_import_header ? ImportHeader::read(fs).machine_type() : CoffFileHeader::read(fs).machine_type(); machine_types.insert(machine); } diff --git a/toolsrc/src/vcpkg/base/files.cpp b/toolsrc/src/vcpkg/base/files.cpp index b59104a59..3d96e834b 100644 --- a/toolsrc/src/vcpkg/base/files.cpp +++ b/toolsrc/src/vcpkg/base/files.cpp @@ -22,9 +22,9 @@ namespace vcpkg::Files auto length = file_stream.tellg(); file_stream.seekg(0, file_stream.beg); - if (length > SIZE_MAX) + if (length == std::streampos(-1)) { - return std::make_error_code(std::errc::file_too_large); + return std::make_error_code(std::errc::io_error); } std::string output; @@ -55,17 +55,18 @@ namespace vcpkg::Files virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const std::string& filename) const override { + static const fs::path UNIX_ROOT = "/"; fs::path current_dir = starting_dir; - for (; !current_dir.empty(); current_dir = current_dir.parent_path()) + for (; !current_dir.empty() && current_dir != UNIX_ROOT; current_dir = current_dir.parent_path()) { const fs::path candidate = current_dir / filename; if (exists(candidate)) { - break; + return current_dir; } } - return current_dir; + return fs::path(); } virtual std::vector<fs::path> get_files_recursive(const fs::path& dir) const override @@ -108,6 +109,10 @@ namespace vcpkg::Files output.close(); } + virtual void rename(const fs::path& oldpath, const fs::path& newpath, std::error_code& ec) override + { + fs::stdfs::rename(oldpath, newpath, ec); + } virtual void rename(const fs::path& oldpath, const fs::path& newpath) override { fs::stdfs::rename(oldpath, newpath); @@ -181,12 +186,15 @@ namespace vcpkg::Files return; } - auto count = fwrite(data.data(), sizeof(data[0]), data.size(), f); - fclose(f); - - if (count != data.size()) + if (f != nullptr) { - ec = std::make_error_code(std::errc::no_space_on_device); + auto count = fwrite(data.data(), sizeof(data[0]), data.size(), f); + fclose(f); + + if (count != data.size()) + { + ec = std::make_error_code(std::errc::no_space_on_device); + } } } }; diff --git a/toolsrc/src/vcpkg/base/strings.cpp b/toolsrc/src/vcpkg/base/strings.cpp index d912734e3..48dc5ed09 100644 --- a/toolsrc/src/vcpkg/base/strings.cpp +++ b/toolsrc/src/vcpkg/base/strings.cpp @@ -7,10 +7,11 @@ namespace vcpkg::Strings::details { // To disambiguate between two overloads - static const auto isspace = [](const char c) { return std::isspace(c); }; + static bool is_space(const char c) { return std::isspace(c) != 0; } // Avoids C4244 warnings because of char<->int conversion that occur when using std::tolower() static char tolower_char(const char c) { return static_cast<char>(std::tolower(c)); } + static char toupper_char(const char c) { return static_cast<char>(std::toupper(c)); } #if defined(_WIN32) static _locale_t& c_locale() @@ -38,7 +39,7 @@ namespace vcpkg::Strings::details _vsnprintf_s_l(&output.at(0), output.size() + 1, output.size(), fmtstr, c_locale(), args); #else va_start(args, fmtstr); - auto res = vsnprintf(&output.at(0), output.size() + 1, fmtstr, args); + vsnprintf(&output.at(0), output.size() + 1, fmtstr, args); #endif va_end(args); @@ -48,31 +49,29 @@ namespace vcpkg::Strings::details namespace vcpkg::Strings { +#if defined(_WIN32) std::wstring to_utf16(const CStringView& s) { -#if defined(_WIN32) - const int size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0); std::wstring output; + const size_t size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0); + if (size == 0) return output; output.resize(size - 1); - MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, output.data(), size - 1); + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, output.data(), static_cast<int>(size) - 1); return output; -#else - Checks::unreachable(VCPKG_LINE_INFO); -#endif } +#endif - std::string to_utf8(const CWStringView& w) - { #if defined(_WIN32) - const int size = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, nullptr, 0, nullptr, nullptr); + std::string to_utf8(const wchar_t* w) + { std::string output; + const size_t size = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); + if (size == 0) return output; output.resize(size - 1); - WideCharToMultiByte(CP_UTF8, 0, w.c_str(), -1, output.data(), size - 1, nullptr, nullptr); + WideCharToMultiByte(CP_UTF8, 0, w, -1, output.data(), static_cast<int>(size) - 1, nullptr, nullptr); return output; -#else - Checks::unreachable(VCPKG_LINE_INFO); -#endif } +#endif std::string escape_string(const CStringView& s, char char_to_escape, char escape_char) { @@ -108,11 +107,16 @@ namespace vcpkg::Strings #endif } - std::string ascii_to_lowercase(const std::string& input) + std::string ascii_to_lowercase(std::string s) { - std::string output(input); - std::transform(output.begin(), output.end(), output.begin(), &details::tolower_char); - return output; + std::transform(s.begin(), s.end(), s.begin(), &details::tolower_char); + return s; + } + + std::string ascii_to_uppercase(std::string s) + { + std::transform(s.begin(), s.end(), s.begin(), &details::toupper_char); + return s; } bool case_insensitive_ascii_starts_with(const std::string& s, const std::string& pattern) @@ -137,8 +141,8 @@ namespace vcpkg::Strings std::string trim(std::string&& s) { - s.erase(std::find_if_not(s.rbegin(), s.rend(), details::isspace).base(), s.end()); - s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), details::isspace)); + s.erase(std::find_if_not(s.rbegin(), s.rend(), details::is_space).base(), s.end()); + s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), details::is_space)); return std::move(s); } diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp index e55db9461..95c9511f9 100644 --- a/toolsrc/src/vcpkg/base/system.cpp +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -5,10 +5,14 @@ #include <vcpkg/globalstate.h> #include <vcpkg/metrics.h> -#include <time.h> +#include <ctime> #if defined(__APPLE__) -# include <mach-o/dyld.h> +#include <mach-o/dyld.h> +#endif + +#if defined(__FreeBSD__) +#include <sys/sysctl.h> #endif #pragma comment(lib, "Advapi32") @@ -19,7 +23,7 @@ namespace vcpkg::System { using std::chrono::system_clock; std::time_t now_time = system_clock::to_time_t(system_clock::now()); - tm parts; + tm parts{}; #if defined(_WIN32) localtime_s(&parts, &now_time); #else @@ -35,14 +39,22 @@ namespace vcpkg::System const int bytes = GetModuleFileNameW(nullptr, buf, _MAX_PATH); if (bytes == 0) std::abort(); return fs::path(buf, buf + bytes); -#elif __APPLE__ +#elif defined(__APPLE__) uint32_t size = 1024 * 32; char buf[size] = {}; bool result = _NSGetExecutablePath(buf, &size); Checks::check_exit(VCPKG_LINE_INFO, result != -1, "Could not determine current executable path."); - std::unique_ptr<char> canonicalPath (realpath(buf, NULL)); + std::unique_ptr<char> canonicalPath(realpath(buf, NULL)); Checks::check_exit(VCPKG_LINE_INFO, result != -1, "Could not determine current executable path."); return fs::path(std::string(canonicalPath.get())); +#elif defined(__FreeBSD__) + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + char exePath[2048]; + size_t len = sizeof(exePath); + auto rcode = sysctl(mib, 4, exePath, &len, NULL, 0); + Checks::check_exit(VCPKG_LINE_INFO, rcode == 0, "Could not determine current executable path."); + Checks::check_exit(VCPKG_LINE_INFO, len > 0, "Could not determine current executable path."); + return fs::path(exePath, exePath + len - 1); #else /* LINUX */ std::array<char, 1024 * 4> buf; auto written = readlink("/proc/self/exe", buf.data(), buf.size()); @@ -141,12 +153,12 @@ namespace vcpkg::System R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' %s}")", script_path.u8string(), args); } - int cmd_execute_clean(const CStringView cmd_line) + int cmd_execute_clean(const CStringView cmd_line, const std::unordered_map<std::string, std::string>& extra_env) { #if defined(_WIN32) static const std::string SYSTEM_ROOT = get_environment_variable("SystemRoot").value_or_exit(VCPKG_LINE_INFO); static const std::string SYSTEM_32 = SYSTEM_ROOT + R"(\system32)"; - static const std::string NEW_PATH = Strings::format( + std::string new_path = Strings::format( R"(Path=%s;%s;%s\Wbem;%s\WindowsPowerShell\v1.0\)", SYSTEM_32, SYSTEM_ROOT, SYSTEM_32, SYSTEM_32); std::vector<std::wstring> env_wstrings = { @@ -202,7 +214,7 @@ namespace vcpkg::System for (auto&& env_wstring : env_wstrings) { - const Optional<std::string> value = System::get_environment_variable(Strings::to_utf8(env_wstring)); + const Optional<std::string> value = System::get_environment_variable(Strings::to_utf8(env_wstring.c_str())); const auto v = value.get(); if (!v || v->empty()) continue; @@ -212,11 +224,22 @@ namespace vcpkg::System env_cstr.push_back(L'\0'); } - env_cstr.append(Strings::to_utf16(NEW_PATH)); + if (extra_env.find("PATH") != extra_env.end()) + new_path += Strings::format(";%s", extra_env.find("PATH")->second); + env_cstr.append(Strings::to_utf16(new_path)); env_cstr.push_back(L'\0'); env_cstr.append(L"VSLANG=1033"); env_cstr.push_back(L'\0'); + for (const auto& item : extra_env) + { + if (item.first == "PATH") continue; + env_cstr.append(Strings::to_utf16(item.first)); + env_cstr.push_back(L'='); + env_cstr.append(Strings::to_utf16(item.second)); + env_cstr.push_back(L'\0'); + } + STARTUPINFOW startup_info; memset(&startup_info, 0, sizeof(STARTUPINFOW)); startup_info.cb = sizeof(STARTUPINFOW); @@ -251,8 +274,11 @@ namespace vcpkg::System Debug::println("CreateProcessW() returned %lu", exit_code); return static_cast<int>(exit_code); #else + Debug::println("system(%s)", cmd_line.c_str()); fflush(nullptr); - return system(cmd_line.c_str()); + int rc = system(cmd_line.c_str()); + Debug::println("system() returned %d", rc); + return rc; #endif } @@ -262,35 +288,26 @@ namespace vcpkg::System fflush(nullptr); // Basically we are wrapping it in quotes - const std::string& actual_cmd_line = Strings::format(R"###("%s")###", cmd_line); #if defined(_WIN32) + const std::string& actual_cmd_line = Strings::format(R"###("%s")###", cmd_line); Debug::println("_wsystem(%s)", actual_cmd_line); const int exit_code = _wsystem(Strings::to_utf16(actual_cmd_line).c_str()); Debug::println("_wsystem() returned %d", exit_code); #else - Debug::println("_system(%s)", actual_cmd_line); - const int exit_code = system(actual_cmd_line.c_str()); + Debug::println("_system(%s)", cmd_line); + const int exit_code = system(cmd_line.c_str()); Debug::println("_system() returned %d", exit_code); #endif return exit_code; } - // On Win7, output from powershell calls contain a byte order mark, so we strip it out if it is present - static void remove_byte_order_marks(std::wstring* s) - { - const wchar_t* a = s->c_str(); - // This is the UTF-8 byte-order mark - while (s->size() >= 3 && a[0] == 0xEF && a[1] == 0xBB && a[2] == 0xBF) - { - s->erase(0, 3); - } - } - ExitCodeAndOutput cmd_execute_and_capture_output(const CStringView cmd_line) { // Flush stdout before launching external process fflush(stdout); + auto timer = Chrono::ElapsedTimer::create_started(); + #if defined(_WIN32) const auto actual_cmd_line = Strings::format(R"###("%s 2>&1")###", cmd_line); @@ -300,7 +317,7 @@ namespace vcpkg::System const auto pipe = _wpopen(Strings::to_utf16(actual_cmd_line).c_str(), L"r"); if (pipe == nullptr) { - return {1, Strings::to_utf8(output)}; + return {1, Strings::to_utf8(output.c_str())}; } while (fgetws(buf, 1024, pipe)) { @@ -308,13 +325,22 @@ namespace vcpkg::System } if (!feof(pipe)) { - return {1, Strings::to_utf8(output)}; + return {1, Strings::to_utf8(output.c_str())}; } const auto ec = _pclose(pipe); - Debug::println("_pclose() returned %d", ec); - remove_byte_order_marks(&output); - return {ec, Strings::to_utf8(output)}; + + // On Win7, output from powershell calls contain a utf-8 byte order mark in the utf-16 stream, so we strip it + // out if it is present. 0xEF,0xBB,0xBF is the UTF-8 byte-order mark + const wchar_t* a = output.c_str(); + while (output.size() >= 3 && a[0] == 0xEF && a[1] == 0xBB && a[2] == 0xBF) + { + output.erase(0, 3); + } + + Debug::println("_pclose() returned %d after %8d us", ec, static_cast<int>(timer.microseconds())); + + return {ec, Strings::to_utf8(output.c_str())}; #else const auto actual_cmd_line = Strings::format(R"###(%s 2>&1)###", cmd_line); @@ -336,7 +362,9 @@ namespace vcpkg::System } const auto ec = pclose(pipe); - Debug::println("pclose() returned %d", ec); + + Debug::println("_pclose() returned %d after %8d us", ec, (int)timer.microseconds()); + return {ec, output}; #endif } @@ -452,7 +480,7 @@ namespace vcpkg::System const auto sz2 = GetEnvironmentVariableW(w_varname.c_str(), ret.data(), static_cast<DWORD>(ret.size())); Checks::check_exit(VCPKG_LINE_INFO, sz2 + 1 == sz); ret.pop_back(); - return Strings::to_utf8(ret); + return Strings::to_utf8(ret.c_str()); #else auto v = getenv(varname.c_str()); if (!v) return nullopt; @@ -493,7 +521,7 @@ namespace vcpkg::System return nullopt; ret.pop_back(); // remove extra trailing null byte - return Strings::to_utf8(ret); + return Strings::to_utf8(ret.c_str()); } #else Optional<std::string> get_registry_string(void* base_hkey, const CStringView sub_key, const CStringView valuename) @@ -502,32 +530,41 @@ namespace vcpkg::System } #endif - static const fs::path& get_program_files() + static const Optional<fs::path>& get_program_files() { - static const fs::path PATH = System::get_environment_variable("PROGRAMFILES").value_or_exit(VCPKG_LINE_INFO); + static const auto PATH = []() -> Optional<fs::path> { + auto value = System::get_environment_variable("PROGRAMFILES"); + if (auto v = value.get()) + { + return *v; + } + + return nullopt; + }(); + return PATH; } - const fs::path& get_program_files_32_bit() + const Optional<fs::path>& get_program_files_32_bit() { - static const fs::path PATH = []() -> fs::path { + static const auto PATH = []() -> Optional<fs::path> { auto value = System::get_environment_variable("ProgramFiles(x86)"); if (auto v = value.get()) { - return std::move(*v); + return *v; } return get_program_files(); }(); return PATH; } - const fs::path& get_program_files_platform_bitness() + const Optional<fs::path>& get_program_files_platform_bitness() { - static const fs::path PATH = []() -> fs::path { + static const auto PATH = []() -> Optional<fs::path> { auto value = System::get_environment_variable("ProgramW6432"); if (auto v = value.get()) { - return std::move(*v); + return *v; } return get_program_files(); }(); diff --git a/toolsrc/src/vcpkg/binaryparagraph.cpp b/toolsrc/src/vcpkg/binaryparagraph.cpp index 7a8b0d577..73ca23df1 100644 --- a/toolsrc/src/vcpkg/binaryparagraph.cpp +++ b/toolsrc/src/vcpkg/binaryparagraph.cpp @@ -79,7 +79,7 @@ namespace vcpkg } BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const FeatureParagraph& fpgh, const Triplet& triplet) - : version(), feature(fpgh.name), description(fpgh.description), maintainer() + : version(), description(fpgh.description), maintainer(), feature(fpgh.name) { this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); this->depends = filter_dependencies(fpgh.depends, triplet); diff --git a/toolsrc/src/vcpkg/build.cpp b/toolsrc/src/vcpkg/build.cpp index 408dde798..1ed5e744a 100644 --- a/toolsrc/src/vcpkg/build.cpp +++ b/toolsrc/src/vcpkg/build.cpp @@ -64,7 +64,8 @@ namespace vcpkg::Build::Command const Build::BuildPackageOptions build_package_options{Build::UseHeadVersion::NO, Build::AllowDownloads::YES, Build::CleanBuildtrees::NO, - Build::CleanPackages::NO}; + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN}; std::set<std::string> features_as_set(full_spec.features.begin(), full_spec.features.end()); features_as_set.emplace("core"); @@ -165,6 +166,19 @@ namespace vcpkg::Build } } + static const std::string NAME_BUILD_IN_DOWNLOAD = "BUILT_IN"; + static const std::string NAME_ARIA2_DOWNLOAD = "ARIA2"; + + const std::string& to_string(DownloadTool tool) + { + switch (tool) + { + case DownloadTool::BUILT_IN: return NAME_BUILD_IN_DOWNLOAD; + case DownloadTool::ARIA2: return NAME_ARIA2_DOWNLOAD; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + Optional<LinkageType> to_linkage_type(const std::string& str) { if (str == "dynamic") return LinkageType::DYNAMIC; @@ -197,13 +211,18 @@ namespace vcpkg::Build for (auto&& host : host_architectures) { - auto it = Util::find_if(toolset.supported_architectures, [&](const ToolsetArchOption& opt) { + const auto it = Util::find_if(toolset.supported_architectures, [&](const ToolsetArchOption& opt) { return host == opt.host_arch && target_arch == opt.target_arch; }); if (it != toolset.supported_architectures.end()) return it->name; } - Checks::exit_with_message(VCPKG_LINE_INFO, "Unsupported toolchain combination %s", target_architecture); + Checks::exit_with_message(VCPKG_LINE_INFO, + "Unsupported toolchain combination. Target was: %s but supported ones were:\n%s", + target_architecture, + Strings::join(",", toolset.supported_architectures, [](const ToolsetArchOption& t) { + return t.name.c_str(); + })); } std::string make_build_env_cmd(const PreBuildInfo& pre_build_info, const Toolset& toolset) @@ -266,19 +285,17 @@ namespace vcpkg::Build { const Triplet& triplet = config.triplet; - auto dep_strings = + const std::vector<std::string> dep_strings = Util::fmap_flatten(config.feature_list, [&](std::string const& feature) -> std::vector<std::string> { if (feature == "core") { return filter_dependencies(config.scf.core_paragraph->depends, triplet); } - auto it = - Util::find_if(config.scf.feature_paragraphs, - [&](std::unique_ptr<FeatureParagraph> const& fpgh) { return fpgh->name == feature; }); - Checks::check_exit(VCPKG_LINE_INFO, it != config.scf.feature_paragraphs.end()); + auto maybe_feature = config.scf.find_feature(feature); + Checks::check_exit(VCPKG_LINE_INFO, maybe_feature.has_value()); - return filter_dependencies(it->get()->depends, triplet); + return filter_dependencies(maybe_feature.get()->depends, triplet); }); auto dep_fspecs = FeatureSpec::from_strings_and_triplet(dep_strings, triplet); @@ -291,7 +308,7 @@ namespace vcpkg::Build if (fspec.feature().empty()) { // reference to default features - auto it = status_db.find_installed(fspec.spec()); + 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 @@ -299,9 +316,9 @@ namespace vcpkg::Build } else { - ret.push_back(FeatureSpec{fspec.spec(), "core"}); + ret.emplace_back(fspec.spec(), "core"); for (auto&& default_feature : it->get()->package.default_features) - ret.push_back(FeatureSpec{fspec.spec(), default_feature}); + ret.emplace_back(fspec.spec(), default_feature); } } else @@ -315,280 +332,334 @@ namespace vcpkg::Build } static ExtendedBuildResult do_build_package(const VcpkgPaths& paths, - const BuildPackageConfig& config, - const StatusParagraphs& status_db) + const PreBuildInfo& pre_build_info, + const PackageSpec& spec, + const std::string& abi_tag, + const BuildPackageConfig& config) { auto& fs = paths.get_filesystem(); - const Triplet& triplet = config.triplet; + const Triplet& triplet = spec.triplet(); - struct AbiEntry - { - std::string key; - std::string value; - }; +#if !defined(_WIN32) + // TODO: remove when vcpkg.exe is in charge for acquiring tools. Change introduced in vcpkg v0.0.107. + // bootstrap should have already downloaded ninja, but making sure it is present in case it was deleted. + vcpkg::Util::unused(paths.get_tool_exe(Tools::NINJA)); +#endif - std::vector<AbiEntry> abi_tag_entries; + const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE); + const fs::path& git_exe_path = paths.get_tool_exe(Tools::GIT); - const PackageSpec spec = - PackageSpec::from_name_and_triplet(config.scf.core_paragraph->name, triplet).value_or_exit(VCPKG_LINE_INFO); + std::string all_features; + for (auto& feature : config.scf.feature_paragraphs) + { + all_features.append(feature->name + ";"); + } - std::vector<FeatureSpec> required_fspecs = compute_required_feature_specs(config, status_db); + const Toolset& toolset = paths.get_toolset(pre_build_info); + const std::string cmd_launch_cmake = System::make_cmake_cmd( + cmake_exe_path, + paths.ports_cmake, + { + {"CMD", "BUILD"}, + {"PORT", config.scf.core_paragraph->name}, + {"CURRENT_PORT_DIR", config.port_dir}, + {"TARGET_TRIPLET", spec.triplet().canonical_name()}, + {"VCPKG_PLATFORM_TOOLSET", toolset.version.c_str()}, + {"VCPKG_USE_HEAD_VERSION", + Util::Enum::to_bool(config.build_package_options.use_head_version) ? "1" : "0"}, + {"_VCPKG_NO_DOWNLOADS", !Util::Enum::to_bool(config.build_package_options.allow_downloads) ? "1" : "0"}, + {"_VCPKG_DOWNLOAD_TOOL", to_string(config.build_package_options.download_tool)}, + {"GIT", git_exe_path}, + {"FEATURES", Strings::join(";", config.feature_list)}, + {"ALL_FEATURES", all_features}, + }); - // 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); + auto command = make_build_env_cmd(pre_build_info, toolset); + if (!command.empty()) command.append(" && "); + command.append(cmd_launch_cmake); - // Find all features that aren't installed. This destroys required_fspecs. - Util::unstable_keep_if(required_fspecs, [&](FeatureSpec const& fspec) { - return !status_db.is_installed(fspec) && fspec.name() != spec.name(); - }); + const auto timer = Chrono::ElapsedTimer::create_started(); + + const int return_code = System::cmd_execute_clean(command); + const auto buildtimeus = timer.microseconds(); + const auto spec_string = spec.to_string(); - if (!required_fspecs.empty()) { - return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(required_fspecs)}; + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->track_buildtime(spec.to_string() + ":[" + Strings::join(",", config.feature_list) + "]", + buildtimeus); + if (return_code != 0) + { + locked_metrics->track_property("error", "build failed"); + locked_metrics->track_property("build_error", spec_string); + return BuildResult::BUILD_FAILED; + } } - // dep_pspecs was not destroyed - for (auto&& pspec : dep_pspecs) + const BuildInfo build_info = read_build_info(fs, paths.build_info_file_path(spec)); + const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); + + auto bcf = create_binary_control_file(*config.scf.core_paragraph, triplet, build_info, abi_tag); + + if (error_count != 0) { - if (pspec == spec) continue; - auto status_it = status_db.find_installed(pspec); - Checks::check_exit(VCPKG_LINE_INFO, status_it != status_db.end()); - abi_tag_entries.emplace_back( - AbiEntry{status_it->get()->package.spec.name(), status_it->get()->package.abi}); + return BuildResult::POST_BUILD_CHECKS_FAILED; + } + for (auto&& feature : config.feature_list) + { + 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)); + } } - const fs::path& cmake_exe_path = paths.get_cmake_exe(); - const fs::path& git_exe_path = paths.get_git_exe(); + write_binary_control_file(paths, *bcf); + return {BuildResult::SUCCEEDED, std::move(bcf)}; + } - const fs::path ports_cmake_script_path = paths.ports_cmake; + static ExtendedBuildResult do_build_package_and_clean_buildtrees(const VcpkgPaths& paths, + const PreBuildInfo& pre_build_info, + const PackageSpec& spec, + const std::string& abi_tag, + const BuildPackageConfig& config) + { + auto result = do_build_package(paths, pre_build_info, spec, abi_tag, config); - if (GlobalState::g_binary_caching) + if (config.build_package_options.clean_buildtrees == CleanBuildtrees::YES) { - abi_tag_entries.emplace_back(AbiEntry{ - "portfile", Commands::Hash::get_file_hash(cmake_exe_path, config.port_dir / "portfile.cmake", "SHA1")}); - abi_tag_entries.emplace_back(AbiEntry{ - "control", Commands::Hash::get_file_hash(cmake_exe_path, config.port_dir / "CONTROL", "SHA1")}); + auto& fs = paths.get_filesystem(); + const fs::path buildtrees_dir = paths.buildtrees / config.scf.core_paragraph->name; + auto buildtree_files = fs.get_files_non_recursive(buildtrees_dir); + for (auto&& file : buildtree_files) + { + if (fs.is_directory(file) && file.filename() != "src") + { + std::error_code ec; + fs.remove_all(file, ec); + } + } } - const auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet); + return result; + } + + Optional<AbiTagAndFile> compute_abi_tag(const VcpkgPaths& paths, + const BuildPackageConfig& config, + const PreBuildInfo& pre_build_info, + Span<const AbiEntry> dependency_abis) + { + if (!GlobalState::g_binary_caching) return nullopt; + + auto& fs = paths.get_filesystem(); + const Triplet& triplet = config.triplet; + const std::string& name = config.scf.core_paragraph->name; + + std::vector<AbiEntry> abi_tag_entries; + + abi_tag_entries.insert(abi_tag_entries.end(), dependency_abis.begin(), dependency_abis.end()); + + abi_tag_entries.emplace_back( + AbiEntry{"portfile", Commands::Hash::get_file_hash(paths, config.port_dir / "portfile.cmake", "SHA1")}); + abi_tag_entries.emplace_back( + AbiEntry{"control", Commands::Hash::get_file_hash(paths, config.port_dir / "CONTROL", "SHA1")}); + abi_tag_entries.emplace_back(AbiEntry{"triplet", pre_build_info.triplet_abi_tag}); - std::string features = Strings::join(";", config.feature_list); + const std::string features = Strings::join(";", config.feature_list); abi_tag_entries.emplace_back(AbiEntry{"features", features}); if (config.build_package_options.use_head_version == UseHeadVersion::YES) abi_tag_entries.emplace_back(AbiEntry{"head", ""}); - std::string full_abi_info = - Strings::join("", abi_tag_entries, [](const AbiEntry& p) { return p.key + " " + p.value + "\n"; }); + Util::sort(abi_tag_entries); - std::string abi_tag; + const std::string full_abi_info = + Strings::join("", abi_tag_entries, [](const AbiEntry& p) { return p.key + " " + p.value + "\n"; }); - if (GlobalState::g_binary_caching) + if (GlobalState::debugging) { - if (GlobalState::debugging) + System::println("[DEBUG] <abientries>"); + for (auto&& entry : abi_tag_entries) { - System::println("[DEBUG] <abientries>"); - for (auto&& entry : abi_tag_entries) - { - System::println("[DEBUG] %s|%s", entry.key, entry.value); - } - System::println("[DEBUG] </abientries>"); + System::println("[DEBUG] %s|%s", entry.key, entry.value); } + System::println("[DEBUG] </abientries>"); + } - auto abi_tag_entries_missing = abi_tag_entries; - Util::stable_keep_if(abi_tag_entries_missing, [](const AbiEntry& p) { return p.value.empty(); }); + auto abi_tag_entries_missing = abi_tag_entries; + Util::stable_keep_if(abi_tag_entries_missing, [](const AbiEntry& p) { return p.value.empty(); }); - if (abi_tag_entries_missing.empty()) - { - std::error_code ec; - fs.create_directories(paths.buildtrees / spec.name(), ec); - auto abi_file_path = paths.buildtrees / spec.name() / "vcpkg_abi_info"; - fs.write_contents(abi_file_path, full_abi_info); + if (abi_tag_entries_missing.empty()) + { + std::error_code ec; + fs.create_directories(paths.buildtrees / name, ec); + const auto abi_file_path = paths.buildtrees / name / (triplet.canonical_name() + ".vcpkg_abi_info.txt"); + fs.write_contents(abi_file_path, full_abi_info); - abi_tag = Commands::Hash::get_file_hash(paths.get_cmake_exe(), abi_file_path, "SHA1"); - } - else - { - System::println("Warning: binary caching disabled because abi keys are missing values:\n%s", - Strings::join("", abi_tag_entries_missing, [](const AbiEntry& e) { - return " " + e.key + "\n"; - })); - } + return AbiTagAndFile{Commands::Hash::get_file_hash(paths, abi_file_path, "SHA1"), abi_file_path}; } - std::unique_ptr<BinaryControlFile> bcf; + System::println( + "Warning: binary caching disabled because abi keys are missing values:\n%s", + Strings::join("", abi_tag_entries_missing, [](const AbiEntry& e) { return " " + e.key + "\n"; })); - auto archives_dir = paths.root / "archives"; - if (!abi_tag.empty()) - { - archives_dir /= abi_tag.substr(0, 2); - } - auto archive_path = archives_dir / (abi_tag + ".zip"); + return nullopt; + } - if (GlobalState::g_binary_caching && !abi_tag.empty() && fs.exists(archive_path)) - { - System::println("Using cached binary package: %s", archive_path.u8string()); + static void decompress_archive(const VcpkgPaths& paths, const PackageSpec& spec, const fs::path& archive_path) + { + auto& fs = paths.get_filesystem(); - auto pkg_path = paths.package_dir(spec); - std::error_code ec; - fs.remove_all(pkg_path, ec); - fs.create_directories(pkg_path, ec); - auto files = fs.get_files_non_recursive(pkg_path); - Checks::check_exit(VCPKG_LINE_INFO, files.empty(), "unable to clear path: %s", pkg_path.u8string()); + auto pkg_path = paths.package_dir(spec); + std::error_code ec; + fs.remove_all(pkg_path, ec); + fs.create_directories(pkg_path, ec); + auto files = fs.get_files_non_recursive(pkg_path); + Checks::check_exit(VCPKG_LINE_INFO, files.empty(), "unable to clear path: %s", pkg_path.u8string()); #if defined(_WIN32) - auto&& _7za = paths.get_7za_exe(); + auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP); - System::cmd_execute_clean(Strings::format( - R"("%s" x "%s" -o"%s" -y >nul)", _7za.u8string(), archive_path.u8string(), pkg_path.u8string())); + System::cmd_execute_clean(Strings::format( + R"("%s" x "%s" -o"%s" -y >nul)", seven_zip_exe.u8string(), archive_path.u8string(), pkg_path.u8string())); #else - System::cmd_execute_clean(Strings::format( - R"(unzip -qq "%s" "-d%s")", archive_path.u8string(), pkg_path.u8string())); + System::cmd_execute_clean( + Strings::format(R"(unzip -qq "%s" "-d%s")", archive_path.u8string(), pkg_path.u8string())); #endif + } - auto maybe_bcf = Paragraphs::try_load_cached_control_package(paths, spec); - bcf = std::make_unique<BinaryControlFile>(std::move(maybe_bcf).value_or_exit(VCPKG_LINE_INFO)); - } - else + static void compress_archive(const VcpkgPaths& paths, const PackageSpec& spec, const fs::path& tmp_archive_path) + { + auto& fs = paths.get_filesystem(); + + std::error_code ec; + + fs.remove(tmp_archive_path, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !fs.exists(tmp_archive_path), "Could not remove file: %s", tmp_archive_path.u8string()); +#if defined(_WIN32) + auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP); + + System::cmd_execute_clean(Strings::format(R"("%s" a "%s" "%s\*" >nul)", + seven_zip_exe.u8string(), + tmp_archive_path.u8string(), + paths.package_dir(spec).u8string())); +#else + System::cmd_execute_clean(Strings::format( + R"(cd '%s' && zip --quiet -r '%s' *)", paths.package_dir(spec).u8string(), tmp_archive_path.u8string())); +#endif + } + + ExtendedBuildResult build_package(const VcpkgPaths& paths, + const BuildPackageConfig& config, + const StatusParagraphs& status_db) + { + auto& fs = paths.get_filesystem(); + 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. + Util::unstable_keep_if(required_fspecs, [&](FeatureSpec const& fspec) { + return !status_db.is_installed(fspec) && fspec.name() != name; + }); + + if (!required_fspecs.empty()) { - if (GlobalState::g_binary_caching && !abi_tag.empty()) - { - System::println("Could not locate cached archive: %s", archive_path.u8string()); - } + return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(required_fspecs)}; + } - std::string all_features; - for (auto& feature : config.scf.feature_paragraphs) - { - all_features.append(feature->name + ";"); - } + const PackageSpec spec = + PackageSpec::from_name_and_triplet(config.scf.core_paragraph->name, triplet).value_or_exit(VCPKG_LINE_INFO); - const Toolset& toolset = paths.get_toolset(pre_build_info); - const std::string cmd_launch_cmake = System::make_cmake_cmd( - cmake_exe_path, - ports_cmake_script_path, - { - {"CMD", "BUILD"}, - {"PORT", config.scf.core_paragraph->name}, - {"CURRENT_PORT_DIR", config.port_dir / "/."}, - {"TARGET_TRIPLET", triplet.canonical_name()}, - {"VCPKG_PLATFORM_TOOLSET", toolset.version.c_str()}, - {"VCPKG_USE_HEAD_VERSION", - Util::Enum::to_bool(config.build_package_options.use_head_version) ? "1" : "0"}, - {"_VCPKG_NO_DOWNLOADS", - !Util::Enum::to_bool(config.build_package_options.allow_downloads) ? "1" : "0"}, - {"GIT", git_exe_path}, - {"FEATURES", features}, - {"ALL_FEATURES", all_features}, - }); - - auto command = make_build_env_cmd(pre_build_info, toolset); - if (!command.empty()) command.append(" && "); - command.append(cmd_launch_cmake); - - const auto timer = Chrono::ElapsedTimer::create_started(); - - const int return_code = System::cmd_execute_clean(command); - const auto buildtimeus = timer.microseconds(); - const auto spec_string = spec.to_string(); + std::vector<AbiEntry> dependency_abis; - { - auto locked_metrics = Metrics::g_metrics.lock(); - locked_metrics->track_buildtime(spec.to_string() + ":[" + Strings::join(",", config.feature_list) + "]", - buildtimeus); - if (return_code != 0) - { - locked_metrics->track_property("error", "build failed"); - locked_metrics->track_property("build_error", spec_string); - return BuildResult::BUILD_FAILED; - } - } + // dep_pspecs was not destroyed + for (auto&& pspec : dep_pspecs) + { + if (pspec == spec) continue; + const auto status_it = status_db.find_installed(pspec); + Checks::check_exit(VCPKG_LINE_INFO, status_it != status_db.end()); + dependency_abis.emplace_back( + AbiEntry{status_it->get()->package.spec.name(), status_it->get()->package.abi}); + } - const BuildInfo build_info = read_build_info(fs, paths.build_info_file_path(spec)); - const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); + const auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet); - bcf = create_binary_control_file(*config.scf.core_paragraph, triplet, build_info, abi_tag); + auto maybe_abi_tag_and_file = compute_abi_tag(paths, config, pre_build_info, dependency_abis); - if (error_count != 0) - { - return BuildResult::POST_BUILD_CHECKS_FAILED; - } - for (auto&& feature : config.feature_list) + const auto abi_tag_and_file = maybe_abi_tag_and_file.get(); + + if (GlobalState::g_binary_caching && abi_tag_and_file) + { + const fs::path archives_root_dir = paths.root / "archives"; + const std::string archive_name = abi_tag_and_file->tag + ".zip"; + const fs::path archive_subpath = fs::u8path(abi_tag_and_file->tag.substr(0, 2)) / archive_name; + const fs::path archive_path = archives_root_dir / archive_subpath; + const fs::path archive_tombstone_path = archives_root_dir / "fail" / archive_subpath; + + if (fs.exists(archive_path)) { - 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)); - } + System::println("Using cached binary package: %s", archive_path.u8string()); + + decompress_archive(paths, spec, archive_path); + + auto maybe_bcf = Paragraphs::try_load_cached_package(paths, spec); + std::unique_ptr<BinaryControlFile> bcf = + std::make_unique<BinaryControlFile>(std::move(maybe_bcf).value_or_exit(VCPKG_LINE_INFO)); + return {BuildResult::SUCCEEDED, std::move(bcf)}; } - if (GlobalState::g_binary_caching && !abi_tag.empty()) + if (fs.exists(archive_tombstone_path)) { - std::error_code ec; - fs.write_contents( - paths.package_dir(spec) / "share" / spec.name() / "vcpkg_abi_info.txt", full_abi_info, ec); + System::println("Found failure tombstone: %s", archive_tombstone_path.u8string()); + return BuildResult::BUILD_FAILED; } - write_binary_control_file(paths, *bcf); + System::println("Could not locate cached archive: %s", archive_path.u8string()); - if (GlobalState::g_binary_caching && !abi_tag.empty()) - { - auto tmp_archive_path = paths.buildtrees / spec.name() / (spec.triplet().to_string() + ".zip"); - - std::error_code ec; - fs.remove(tmp_archive_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !fs.exists(tmp_archive_path), - "Could not remove file: %s", - tmp_archive_path.u8string()); -#if defined(_WIN32) - auto&& _7za = paths.get_7za_exe(); + ExtendedBuildResult result = do_build_package_and_clean_buildtrees( + paths, pre_build_info, spec, maybe_abi_tag_and_file.value_or(AbiTagAndFile{}).tag, config); - System::cmd_execute_clean(Strings::format( - R"("%s" a "%s" "%s\*" >nul)", - _7za.u8string(), - tmp_archive_path.u8string(), - paths.package_dir(spec).u8string())); -#else - System::cmd_execute_clean(Strings::format( - R"(cd '%s' && zip --quiet -r '%s' *)", - paths.package_dir(spec).u8string(), - tmp_archive_path.u8string())); -#endif - fs.create_directories(archives_dir, ec); - - fs.rename(tmp_archive_path, archive_path); + std::error_code ec; + fs.create_directories(paths.package_dir(spec) / "share" / spec.name(), ec); + auto abi_file_in_package = paths.package_dir(spec) / "share" / spec.name() / "vcpkg_abi_info.txt"; + fs.copy_file(abi_tag_and_file->tag_file, abi_file_in_package, fs::stdfs::copy_options::none, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not copy into file: %s", abi_file_in_package.u8string()); - System::println("Stored binary cache: %s", archive_path.u8string()); - } - } - return {BuildResult::SUCCEEDED, std::move(bcf)}; - } + if (result.code == BuildResult::SUCCEEDED) + { + const auto tmp_archive_path = paths.buildtrees / spec.name() / (spec.triplet().to_string() + ".zip"); - ExtendedBuildResult build_package(const VcpkgPaths& paths, - const BuildPackageConfig& config, - const StatusParagraphs& status_db) - { - ExtendedBuildResult result = do_build_package(paths, config, status_db); + compress_archive(paths, spec, tmp_archive_path); - if (config.build_package_options.clean_buildtrees == CleanBuildtrees::YES) - { - auto& fs = paths.get_filesystem(); - const fs::path buildtrees_dir = paths.buildtrees / config.scf.core_paragraph->name; - auto buildtree_files = fs.get_files_non_recursive(buildtrees_dir); - for (auto&& file : buildtree_files) + fs.create_directories(archive_path.parent_path(), ec); + fs.rename(tmp_archive_path, archive_path, ec); + if (ec) + System::println( + System::Color::warning, "Failed to store binary cache: %s", archive_path.u8string()); + else + System::println("Stored binary cache: %s", archive_path.u8string()); + } + else if (result.code == BuildResult::BUILD_FAILED || result.code == BuildResult::POST_BUILD_CHECKS_FAILED) { - if (fs.is_directory(file) && file.filename() != "src") - { - std::error_code ec; - fs.remove_all(file, ec); - } + // Build failed, so store tombstone archive + fs.create_directories(archive_tombstone_path.parent_path(), ec); + fs.write_contents(archive_tombstone_path, "", ec); } + + return result; } - return result; + return do_build_package_and_clean_buildtrees( + paths, pre_build_info, spec, maybe_abi_tag_and_file.value_or(AbiTagAndFile{}).tag, config); } const std::string& to_string(const BuildResult build_result) @@ -698,11 +769,11 @@ namespace vcpkg::Build { static constexpr CStringView FLAG_GUID = "c35112b6-d1ba-415b-aa5d-81de856ef8eb"; - const fs::path& cmake_exe_path = paths.get_cmake_exe(); + 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.triplets / (triplet.canonical_name() + ".cmake"); - auto triplet_abi_tag = [&]() { + const std::string triplet_abi_tag = [&]() { static std::map<fs::path, std::string> s_hash_cache; if (GlobalState::g_binary_caching) @@ -712,7 +783,7 @@ namespace vcpkg::Build { return it_hash->second; } - auto hash = Commands::Hash::get_file_hash(paths.get_cmake_exe(), triplet_file_path, "SHA1"); + auto hash = Commands::Hash::get_file_hash(paths, triplet_file_path, "SHA1"); s_hash_cache.emplace(triplet_file_path, hash); return hash; } diff --git a/toolsrc/src/vcpkg/commands.cache.cpp b/toolsrc/src/vcpkg/commands.cache.cpp index 85bf5fb4d..a9d8ba03c 100644 --- a/toolsrc/src/vcpkg/commands.cache.cpp +++ b/toolsrc/src/vcpkg/commands.cache.cpp @@ -38,7 +38,7 @@ namespace vcpkg::Commands::Cache void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const std::vector<BinaryParagraph> binary_paragraphs = read_all_binary_paragraphs(paths); if (binary_paragraphs.empty()) diff --git a/toolsrc/src/vcpkg/commands.ci.cpp b/toolsrc/src/vcpkg/commands.ci.cpp index c43f25b40..e2b93dc7e 100644 --- a/toolsrc/src/vcpkg/commands.ci.cpp +++ b/toolsrc/src/vcpkg/commands.ci.cpp @@ -1,5 +1,6 @@ #include "pch.h" +#include <vcpkg/base/cache.h> #include <vcpkg/base/files.h> #include <vcpkg/base/stringliteral.h> #include <vcpkg/base/system.h> @@ -7,6 +8,7 @@ #include <vcpkg/build.h> #include <vcpkg/commands.h> #include <vcpkg/dependencies.h> +#include <vcpkg/globalstate.h> #include <vcpkg/help.h> #include <vcpkg/input.h> #include <vcpkg/install.h> @@ -24,6 +26,7 @@ namespace vcpkg::Commands::CI Install::InstallSummary summary; }; + static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; static constexpr StringLiteral OPTION_EXCLUDE = "--exclude"; static constexpr StringLiteral OPTION_XUNIT = "--x-xunit"; @@ -32,16 +35,130 @@ namespace vcpkg::Commands::CI {OPTION_XUNIT, "File to output results in XUnit format (internal)"}, }}; + static constexpr std::array<CommandSwitch, 1> CI_SWITCHES = { + {{OPTION_DRY_RUN, "Print out plan without execution"}}}; + const CommandStructure COMMAND_STRUCTURE = { Help::create_example_string("ci x64-windows"), - 0, + 1, SIZE_MAX, - {{}, CI_SETTINGS}, + {CI_SWITCHES, CI_SETTINGS}, nullptr, }; + UnknownCIPortsResults find_unknown_ports_for_ci(const VcpkgPaths& paths, + const std::set<std::string>& exclusions, + const Dependencies::PortFileProvider& provider, + const std::vector<FeatureSpec>& fspecs) + { + UnknownCIPortsResults ret; + + auto& fs = paths.get_filesystem(); + + std::map<PackageSpec, std::string> abi_tag_map; + std::set<PackageSpec> will_fail; + + const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::YES, + Build::CleanPackages::YES}; + + vcpkg::Cache<Triplet, Build::PreBuildInfo> pre_build_info_cache; + + auto action_plan = Dependencies::create_feature_install_plan(provider, fspecs, StatusParagraphs{}); + + for (auto&& action : action_plan) + { + if (auto p = action.install_action.get()) + { + // determine abi tag + std::string abi; + if (auto scf = p->source_control_file.get()) + { + auto triplet = p->spec.triplet(); + + const Build::BuildPackageConfig build_config{ + *scf, triplet, paths.port_dir(p->spec), install_plan_options, p->feature_list}; + + auto dependency_abis = + Util::fmap(p->computed_dependencies, [&](const PackageSpec& spec) -> Build::AbiEntry { + auto it = abi_tag_map.find(spec); + + if (it == 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); }); + + 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; + abi_tag_map.emplace(p->spec, abi); + } + } + else if (auto ipv = p->installed_package.get()) + { + abi = ipv->core->package.abi; + if (!abi.empty()) 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; + + bool b_will_build = false; + + 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); + b_will_build = true; + } + + System::println("%40s: %1s %8s: %s", p->spec, (b_will_build ? "*" : " "), state, abi); + } + } + + return ret; + } + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) { + if (!GlobalState::g_binary_caching) + { + System::println(System::Color::warning, "Warning: Running ci without binary caching!"); + } + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); std::set<std::string> exclusions_set; @@ -52,6 +169,8 @@ namespace vcpkg::Commands::CI exclusions_set.insert(exclusions.begin(), exclusions.end()); } + auto is_dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); + std::vector<Triplet> triplets; for (const std::string& triplet : args.command_arguments) { @@ -71,16 +190,21 @@ namespace vcpkg::Commands::CI Build::CleanBuildtrees::YES, Build::CleanPackages::YES}; - std::vector<std::string> ports = Install::get_all_port_names(paths); + std::vector<std::map<PackageSpec, BuildResult>> all_known_results; + + std::vector<std::string> all_ports = Install::get_all_port_names(paths); std::vector<TripletAndSummary> results; for (const Triplet& triplet : triplets) { Input::check_triplet(triplet, paths); - std::vector<PackageSpec> specs = PackageSpec::to_package_specs(ports, triplet); + + std::vector<PackageSpec> specs = PackageSpec::to_package_specs(all_ports, triplet); // Install the default features for every package - const auto featurespecs = Util::fmap(specs, [](auto& spec) { return FeatureSpec(spec, ""); }); + auto all_fspecs = Util::fmap(specs, [](auto& spec) { return FeatureSpec(spec, ""); }); + auto split_specs = find_unknown_ports_for_ci(paths, exclusions_set, paths_port_file, all_fspecs); + auto fspecs = Util::fmap(split_specs.unknown, [](auto& spec) { return FeatureSpec(spec, ""); }); - auto action_plan = Dependencies::create_feature_install_plan(paths_port_file, featurespecs, status_db); + auto action_plan = Dependencies::create_feature_install_plan(paths_port_file, fspecs, status_db); for (auto&& action : action_plan) { @@ -94,8 +218,18 @@ namespace vcpkg::Commands::CI } } - auto summary = Install::perform(action_plan, Install::KeepGoing::YES, paths, status_db); - results.push_back({triplet, std::move(summary)}); + if (is_dry_run) + { + Dependencies::print_plan(action_plan); + } + else + { + auto summary = Install::perform(action_plan, Install::KeepGoing::YES, paths, status_db); + for (auto&& result : summary.results) + split_specs.known.erase(result.spec); + results.push_back({triplet, std::move(summary)}); + all_known_results.emplace_back(std::move(split_specs.known)); + } } for (auto&& result : results) @@ -112,6 +246,14 @@ namespace vcpkg::Commands::CI for (auto&& result : results) xunit_doc += result.summary.xunit_results(); + for (auto&& known_result : all_known_results) + { + for (auto&& result : known_result) + { + xunit_doc += + Install::InstallSummary::xunit_result(result.first, Chrono::ElapsedTime{}, result.second); + } + } xunit_doc += "</collection></assembly></assemblies>\n"; paths.get_filesystem().write_contents(fs::u8path(it_xunit->second), xunit_doc); diff --git a/toolsrc/src/vcpkg/commands.contact.cpp b/toolsrc/src/vcpkg/commands.contact.cpp index ffed07557..9f86a67dc 100644 --- a/toolsrc/src/vcpkg/commands.contact.cpp +++ b/toolsrc/src/vcpkg/commands.contact.cpp @@ -14,24 +14,28 @@ namespace vcpkg::Commands::Contact return S_EMAIL; } - static const CommandSwitch switches[] = {{"--survey", "Launch default browser to the current vcpkg survey"}}; + static constexpr StringLiteral OPTION_SURVEY = "--survey"; + + static constexpr std::array<CommandSwitch, 1> SWITCHES = {{ + {OPTION_SURVEY, "Launch default browser to the current vcpkg survey"}, + }}; const CommandStructure COMMAND_STRUCTURE = { Help::create_example_string("contact"), 0, 0, - {switches, {}}, + {SWITCHES, {}}, nullptr, }; void perform_and_exit(const VcpkgCmdArguments& args) { - auto parsed_args = args.parse_arguments(COMMAND_STRUCTURE); + const ParsedArguments parsed_args = args.parse_arguments(COMMAND_STRUCTURE); - if (Util::Sets::contains(parsed_args.switches, switches[0].name)) + if (Util::Sets::contains(parsed_args.switches, SWITCHES[0].name)) { auto maybe_now = Chrono::CTime::get_current_date_time(); - if (auto p_now = maybe_now.get()) + if (const auto p_now = maybe_now.get()) { auto& fs = Files::get_real_filesystem(); auto config = UserConfig::try_read_data(fs); diff --git a/toolsrc/src/vcpkg/commands.cpp b/toolsrc/src/vcpkg/commands.cpp index d9c0e54cd..8b6ffb3d7 100644 --- a/toolsrc/src/vcpkg/commands.cpp +++ b/toolsrc/src/vcpkg/commands.cpp @@ -43,7 +43,8 @@ namespace vcpkg::Commands {"portsdiff", &PortsDiff::perform_and_exit}, {"autocomplete", &Autocomplete::perform_and_exit}, {"hash", &Hash::perform_and_exit}, - }; + // {"fetch", &Fetch::perform_and_exit}, + }; return t; } @@ -51,7 +52,7 @@ namespace vcpkg::Commands { static std::vector<PackageNameAndFunction<CommandTypeC>> t = { {"version", &Version::perform_and_exit}, - {"contact", &Contact::perform_and_exit} + {"contact", &Contact::perform_and_exit}, }; return t; } diff --git a/toolsrc/src/vcpkg/commands.create.cpp b/toolsrc/src/vcpkg/commands.create.cpp index 25c34cf09..dfb3ab784 100644 --- a/toolsrc/src/vcpkg/commands.create.cpp +++ b/toolsrc/src/vcpkg/commands.create.cpp @@ -18,11 +18,11 @@ namespace vcpkg::Commands::Create void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const std::string port_name = args.command_arguments.at(0); const std::string url = args.command_arguments.at(1); - const fs::path& cmake_exe = paths.get_cmake_exe(); + const fs::path& cmake_exe = paths.get_tool_exe(Tools::CMAKE); std::vector<System::CMakeVariable> cmake_args{{"CMD", "CREATE"}, {"PORT", port_name}, {"URL", url}}; diff --git a/toolsrc/src/vcpkg/commands.dependinfo.cpp b/toolsrc/src/vcpkg/commands.dependinfo.cpp index bb300d96e..1ca658216 100644 --- a/toolsrc/src/vcpkg/commands.dependinfo.cpp +++ b/toolsrc/src/vcpkg/commands.dependinfo.cpp @@ -19,7 +19,7 @@ namespace vcpkg::Commands::DependInfo void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); std::vector<std::unique_ptr<SourceControlFile>> source_control_files = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); @@ -30,7 +30,6 @@ namespace vcpkg::Commands::DependInfo Util::erase_remove_if(source_control_files, [&](const std::unique_ptr<SourceControlFile>& source_control_file) { - const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; if (Strings::case_insensitive_ascii_contains(source_paragraph.name, filter)) diff --git a/toolsrc/src/vcpkg/commands.edit.cpp b/toolsrc/src/vcpkg/commands.edit.cpp index 41f261a53..b6324565c 100644 --- a/toolsrc/src/vcpkg/commands.edit.cpp +++ b/toolsrc/src/vcpkg/commands.edit.cpp @@ -1,5 +1,6 @@ #include "pch.h" +#include <vcpkg/base/strings.h> #include <vcpkg/base/system.h> #include <vcpkg/commands.h> #include <vcpkg/help.h> @@ -9,14 +10,15 @@ namespace vcpkg::Commands::Edit { static std::vector<fs::path> find_from_registry() { + std::vector<fs::path> output; + +#if defined(_WIN32) static const std::array<const char*, 3> REGKEYS = { R"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1)", R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1)", R"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1)", }; - std::vector<fs::path> output; -#if defined(_WIN32) for (auto&& keypath : REGKEYS) { const Optional<std::string> code_installpath = @@ -34,6 +36,8 @@ namespace vcpkg::Commands::Edit static constexpr StringLiteral OPTION_BUILDTREES = "--buildtrees"; + static constexpr StringLiteral OPTION_ALL = "--all"; + static std::vector<std::string> valid_arguments(const VcpkgPaths& paths) { auto sources_and_errors = Paragraphs::try_load_all_ports(paths.get_filesystem(), paths.ports); @@ -42,18 +46,49 @@ namespace vcpkg::Commands::Edit [](auto&& pgh) -> std::string { return pgh->core_paragraph->name; }); } - static constexpr std::array<CommandSwitch, 1> EDIT_SWITCHES = {{ - {OPTION_BUILDTREES, "Open editor into the port-specific buildtree subfolder"}, - }}; + static constexpr std::array<CommandSwitch, 2> EDIT_SWITCHES = { + {{OPTION_BUILDTREES, "Open editor into the port-specific buildtree subfolder"}, + {OPTION_ALL, "Open editor into the port as well as the port-specific buildtree subfolder"}}}; const CommandStructure COMMAND_STRUCTURE = { Help::create_example_string("edit zlib"), 1, - 1, + 10, {EDIT_SWITCHES, {}}, &valid_arguments, }; + static std::vector<std::string> create_editor_arguments(const VcpkgPaths& paths, + const ParsedArguments& options, + const std::vector<std::string>& ports) + { + if (Util::Sets::contains(options.switches, OPTION_ALL)) + { + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { + const auto portpath = paths.ports / port_name; + const auto portfile = portpath / "portfile.cmake"; + const auto buildtrees_current_dir = paths.buildtrees / port_name; + return Strings::format(R"###("%s" "%s" "%s")###", + portpath.u8string(), + portfile.u8string(), + buildtrees_current_dir.u8string()); + }); + } + + if (Util::Sets::contains(options.switches, OPTION_BUILDTREES)) + { + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { + return (paths.buildtrees / port_name).u8string(); + }); + } + + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { + const auto portpath = paths.ports / port_name; + const auto portfile = portpath / "portfile.cmake"; + return Strings::format(R"###("%s" "%s")###", portpath.u8string(), portfile.u8string()); + }); + } + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { static const fs::path VS_CODE_INSIDERS = fs::path{"Microsoft VS Code Insiders"} / "Code - Insiders.exe"; @@ -62,26 +97,40 @@ namespace vcpkg::Commands::Edit auto& fs = paths.get_filesystem(); const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); - const std::string port_name = args.command_arguments.at(0); - const fs::path portpath = paths.ports / port_name; - Checks::check_exit(VCPKG_LINE_INFO, fs.is_directory(portpath), R"(Could not find port named "%s")", port_name); + const std::vector<std::string>& ports = args.command_arguments; + for (auto&& port_name : ports) + { + const fs::path portpath = paths.ports / port_name; + Checks::check_exit( + VCPKG_LINE_INFO, fs.is_directory(portpath), R"(Could not find port named "%s")", port_name); + } std::vector<fs::path> candidate_paths; auto maybe_editor_path = System::get_environment_variable("EDITOR"); - if (auto editor_path = maybe_editor_path.get()) + if (const std::string* editor_path = maybe_editor_path.get()) { candidate_paths.emplace_back(*editor_path); } - candidate_paths.push_back(System::get_program_files_platform_bitness() / VS_CODE_INSIDERS); - candidate_paths.push_back(System::get_program_files_32_bit() / VS_CODE_INSIDERS); - candidate_paths.push_back(System::get_program_files_platform_bitness() / VS_CODE); - candidate_paths.push_back(System::get_program_files_32_bit() / VS_CODE); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const fs::path* pf = program_files.get()) + { + candidate_paths.push_back(*pf / VS_CODE_INSIDERS); + candidate_paths.push_back(*pf / VS_CODE); + } + + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const fs::path* pf = program_files_32_bit.get()) + { + candidate_paths.push_back(*pf / VS_CODE_INSIDERS); + candidate_paths.push_back(*pf / VS_CODE); + } const std::vector<fs::path> from_registry = find_from_registry(); candidate_paths.insert(candidate_paths.end(), from_registry.cbegin(), from_registry.cend()); - auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { return fs.exists(p); }); + const auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { return fs.exists(p); }); if (it == candidate_paths.cend()) { System::println( @@ -94,20 +143,9 @@ namespace vcpkg::Commands::Edit } const fs::path env_editor = *it; - if (Util::Sets::contains(options.switches, OPTION_BUILDTREES)) - { - const auto buildtrees_current_dir = paths.buildtrees / port_name; - - const auto cmd_line = - Strings::format(R"("%s" "%s" -n)", env_editor.u8string(), buildtrees_current_dir.u8string()); - Checks::exit_with_code(VCPKG_LINE_INFO, System::cmd_execute(cmd_line)); - } - - const auto cmd_line = Strings::format( - R"("%s" "%s" "%s" -n)", - env_editor.u8string(), - portpath.u8string(), - (portpath / "portfile.cmake").u8string()); + const std::vector<std::string> arguments = create_editor_arguments(paths, options, ports); + const auto args_as_string = Strings::join(" ", arguments); + const auto cmd_line = Strings::format(R"("%s" %s -n)", env_editor.u8string(), args_as_string); Checks::exit_with_code(VCPKG_LINE_INFO, System::cmd_execute(cmd_line)); } } diff --git a/toolsrc/src/vcpkg/commands.env.cpp b/toolsrc/src/vcpkg/commands.env.cpp index f3beef6cd..d078baedb 100644 --- a/toolsrc/src/vcpkg/commands.env.cpp +++ b/toolsrc/src/vcpkg/commands.env.cpp @@ -1,5 +1,6 @@ #include "pch.h" +#include <vcpkg/base/strings.h> #include <vcpkg/base/system.h> #include <vcpkg/build.h> #include <vcpkg/commands.h> @@ -7,25 +8,66 @@ namespace vcpkg::Commands::Env { + static constexpr StringLiteral OPTION_BIN = "--bin"; + static constexpr StringLiteral OPTION_INCLUDE = "--include"; + static constexpr StringLiteral OPTION_DEBUG_BIN = "--debug-bin"; + static constexpr StringLiteral OPTION_TOOLS = "--tools"; + static constexpr StringLiteral OPTION_PYTHON = "--python"; + + static constexpr std::array<CommandSwitch, 5> SWITCHES = {{ + {OPTION_BIN, "Add installed bin/ to PATH"}, + {OPTION_INCLUDE, "Add installed include/ to INCLUDE"}, + {OPTION_DEBUG_BIN, "Add installed debug/bin/ to PATH"}, + {OPTION_TOOLS, "Add installed tools/*/ to PATH"}, + {OPTION_PYTHON, "Add installed python/ to PYTHONPATH"}, + }}; + const CommandStructure COMMAND_STRUCTURE = { Help::create_example_string("env --triplet x64-windows"), 0, 0, - {}, + {SWITCHES, {}}, nullptr, }; - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& triplet) { - args.parse_arguments(COMMAND_STRUCTURE); + 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, default_triplet); + const auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, triplet); const Toolset& toolset = paths.get_toolset(pre_build_info); auto env_cmd = Build::make_build_env_cmd(pre_build_info, toolset); + + std::unordered_map<std::string, std::string> extra_env = {}; + const bool add_bin = Util::Sets::contains(options.switches, OPTION_BIN); + const bool add_include = Util::Sets::contains(options.switches, OPTION_INCLUDE); + const bool add_debug_bin = Util::Sets::contains(options.switches, OPTION_DEBUG_BIN); + const bool add_tools = Util::Sets::contains(options.switches, OPTION_TOOLS); + const bool add_python = Util::Sets::contains(options.switches, OPTION_PYTHON); + + std::vector<std::string> path_vars; + if (add_bin) path_vars.push_back((paths.installed / triplet.to_string() / "bin").u8string()); + if (add_debug_bin) path_vars.push_back((paths.installed / triplet.to_string() / "debug" / "bin").u8string()); + if (add_include) extra_env.emplace("INCLUDE", (paths.installed / triplet.to_string() / "include").u8string()); + if (add_tools) + { + auto tools_dir = paths.installed / triplet.to_string() / "tools"; + auto tool_files = fs.get_files_non_recursive(tools_dir); + path_vars.push_back(tools_dir.u8string()); + for (auto&& tool_dir : tool_files) + { + if (fs.is_directory(tool_dir)) path_vars.push_back(tool_dir.u8string()); + } + } + if (add_python) extra_env.emplace("PYTHONPATH", (paths.installed / triplet.to_string() / "python").u8string()); + if (path_vars.size() > 0) extra_env.emplace("PATH", Strings::join(";", path_vars)); + if (env_cmd.empty()) - System::cmd_execute_clean("cmd"); + System::cmd_execute_clean("cmd", extra_env); else - System::cmd_execute_clean(env_cmd + " && cmd"); + System::cmd_execute_clean(env_cmd + " && cmd", extra_env); Checks::exit_success(VCPKG_LINE_INFO); } diff --git a/toolsrc/src/vcpkg/commands.exportifw.cpp b/toolsrc/src/vcpkg/commands.exportifw.cpp index 58d9aa0be..ae106196a 100644 --- a/toolsrc/src/vcpkg/commands.exportifw.cpp +++ b/toolsrc/src/vcpkg/commands.exportifw.cpp @@ -291,7 +291,7 @@ namespace vcpkg::Export::IFW std::error_code ec; Files::Filesystem& fs = paths.get_filesystem(); - const fs::path& installerbase_exe = paths.get_ifw_installerbase_exe(); + const fs::path& installerbase_exe = paths.get_tool_exe(Tools::IFW_INSTALLER_BASE); fs::path tempmaintenancetool = ifw_packages_dir_path / "maintenance" / "data" / "tempmaintenancetool.exe"; fs.create_directories(tempmaintenancetool.parent_path(), ec); Checks::check_exit(VCPKG_LINE_INFO, @@ -335,7 +335,7 @@ namespace vcpkg::Export::IFW void do_repository(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) { - const fs::path& repogen_exe = paths.get_ifw_repogen_exe(); + const fs::path& repogen_exe = paths.get_tool_exe(Tools::IFW_REPOGEN); const fs::path packages_dir = get_packages_dir_path(export_id, ifw_options, paths); const fs::path repository_dir = get_repository_dir_path(export_id, ifw_options, paths); @@ -361,7 +361,7 @@ namespace vcpkg::Export::IFW void do_installer(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) { - const fs::path& binarycreator_exe = paths.get_ifw_binarycreator_exe(); + const fs::path& binarycreator_exe = paths.get_tool_exe(Tools::IFW_BINARYCREATOR); const fs::path config_file = get_config_file_path(export_id, ifw_options, paths); const fs::path packages_dir = get_packages_dir_path(export_id, ifw_options, paths); const fs::path repository_dir = get_repository_dir_path(export_id, ifw_options, paths); diff --git a/toolsrc/src/vcpkg/commands.fetch.cpp b/toolsrc/src/vcpkg/commands.fetch.cpp new file mode 100644 index 000000000..a6cfec3f0 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.fetch.cpp @@ -0,0 +1,716 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> + +namespace vcpkg::Commands::Fetch +{ + static constexpr CStringView V_120 = "v120"; + static constexpr CStringView V_140 = "v140"; + static constexpr CStringView V_141 = "v141"; + + struct ToolData + { + std::array<int, 3> version; + fs::path exe_path; + std::string url; + fs::path download_path; + fs::path tool_dir_path; + std::string sha512; + }; + + static Optional<std::array<int, 3>> parse_version_string(const std::string& version_as_string) + { + static const std::regex RE(R"###((\d+)\.(\d+)\.(\d+))###"); + + std::match_results<std::string::const_iterator> match; + const auto found = std::regex_search(version_as_string, match, RE); + if (!found) + { + return {}; + } + + const int d1 = atoi(match[1].str().c_str()); + const int d2 = atoi(match[2].str().c_str()); + const int d3 = atoi(match[3].str().c_str()); + const std::array<int, 3> result = {d1, d2, d3}; + return result; + } + + static Optional<std::string> extract_string_between_delimiters(const std::string& input, + const std::string& left_delim, + const std::string& right_delim, + const size_t& starting_offset = 0) + { + const size_t from = input.find(left_delim, starting_offset); + if (from == std::string::npos) return nullopt; + + const size_t substring_start = from + left_delim.length(); + + const size_t to = input.find(right_delim, substring_start); + if (from == std::string::npos) return nullopt; + + return input.substr(substring_start, to - substring_start); + } + + static ToolData parse_tool_data_from_xml(const VcpkgPaths& paths, const std::string& tool) + { +#if defined(_WIN32) + static constexpr StringLiteral OS_STRING = "windows"; +#elif defined(__APPLE__) + static constexpr StringLiteral OS_STRING = "osx"; +#elif defined(__linux__) + static constexpr StringLiteral OS_STRING = "linux"; +#else + return ToolData{}; +#endif + +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + static const std::string XML_VERSION = "2"; + static const fs::path XML_PATH = paths.scripts / "vcpkgTools.xml"; + + const auto get_string_inside_tags = + [](const std::string& input, const std::string& left_delim, const std::string& right_delim) -> std::string { + Optional<std::string> result = extract_string_between_delimiters(input, left_delim, right_delim); + Checks::check_exit(VCPKG_LINE_INFO, + result.has_value(), + "Could not find tag <%s>.*<%s> in %s", + left_delim, + right_delim, + XML_PATH.generic_string()); + + auto r = *result.get(); + return Strings::trim(std::move(r)); + }; + + static const std::regex XML_VERSION_REGEX{R"###(<tools[\s]+version="([^"]+)">)###"}; + static const std::string XML = paths.get_filesystem().read_contents(XML_PATH).value_or_exit(VCPKG_LINE_INFO); + std::smatch match_xml_version; + const bool has_xml_version = std::regex_search(XML.cbegin(), XML.cend(), match_xml_version, XML_VERSION_REGEX); + Checks::check_exit(VCPKG_LINE_INFO, + has_xml_version, + R"(Could not find <tools version="%s"> in %s)", + XML_VERSION, + XML_PATH.generic_string()); + Checks::check_exit(VCPKG_LINE_INFO, + XML_VERSION == match_xml_version[1], + "Expected %s version: [%s], but was [%s]. Please re-run bootstrap-vcpkg.", + XML_PATH.generic_string(), + XML_VERSION, + match_xml_version[1]); + + const std::regex tool_regex{Strings::format(R"###(<tool[\s]+name="%s"[\s]+os="%s">)###", tool, OS_STRING)}; + std::smatch match_tool_entry; + const bool has_tool_entry = std::regex_search(XML.cbegin(), XML.cend(), match_tool_entry, tool_regex); + Checks::check_exit(VCPKG_LINE_INFO, + has_tool_entry, + "Could not find entry for tool [%s] in %s", + tool, + XML_PATH.generic_string()); + + const std::string tool_data = get_string_inside_tags(XML, match_tool_entry[0], R"(</tool>)"); + + const std::string version_as_string = get_string_inside_tags(tool_data, "<version>", R"(</version>)"); + const std::string exe_relative_path = + get_string_inside_tags(tool_data, "<exeRelativePath>", R"(</exeRelativePath>)"); + const std::string url = get_string_inside_tags(tool_data, "<url>", R"(</url>)"); + const std::string sha512 = get_string_inside_tags(tool_data, "<sha512>", R"(</sha512>)"); + auto archive_name = extract_string_between_delimiters(tool_data, "<archiveName>", R"(</archiveName>)"); + + const Optional<std::array<int, 3>> version = parse_version_string(version_as_string); + Checks::check_exit(VCPKG_LINE_INFO, + version.has_value(), + "Could not parse version for tool %s. Version string was: %s", + tool, + version_as_string); + + const std::string tool_dir_name = Strings::format("%s-%s-%s", tool, version_as_string, OS_STRING); + const fs::path tool_dir_path = paths.downloads / "tools" / tool_dir_name; + const fs::path exe_path = tool_dir_path / exe_relative_path; + + return ToolData{*version.get(), + exe_path, + url, + paths.downloads / archive_name.value_or(exe_relative_path), + tool_dir_path, + sha512}; +#endif + } + + static bool exists_and_has_equal_or_greater_version(const std::string& version_cmd, + const std::array<int, 3>& expected_version) + { + const auto rc = System::cmd_execute_and_capture_output(Strings::format(R"(%s)", version_cmd)); + if (rc.exit_code != 0) + { + return false; + } + + const Optional<std::array<int, 3>> v = parse_version_string(rc.output); + if (!v.has_value()) + { + return false; + } + + const std::array<int, 3> actual_version = *v.get(); + return (actual_version[0] > expected_version[0] || + (actual_version[0] == expected_version[0] && actual_version[1] > expected_version[1]) || + (actual_version[0] == expected_version[0] && actual_version[1] == expected_version[1] && + actual_version[2] >= expected_version[2])); + } + + static Optional<fs::path> find_if_has_equal_or_greater_version(const std::vector<fs::path>& candidate_paths, + const std::string& version_check_arguments, + const std::array<int, 3>& expected_version) + { + auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { + const std::string cmd = Strings::format(R"("%s" %s)", p.u8string(), version_check_arguments); + return exists_and_has_equal_or_greater_version(cmd, expected_version); + }); + + if (it != candidate_paths.cend()) + { + return std::move(*it); + } + + return nullopt; + } + + static std::vector<std::string> keep_data_lines(const std::string& data_blob) + { + static const std::regex DATA_LINE_REGEX(R"(<sol>::(.+?)(?=::<eol>))"); + + std::vector<std::string> data_lines; + + const std::sregex_iterator it(data_blob.cbegin(), data_blob.cend(), DATA_LINE_REGEX); + const std::sregex_iterator end; + for (std::sregex_iterator i = it; i != end; ++i) + { + const std::smatch match = *i; + data_lines.push_back(match[1].str()); + } + + return data_lines; + } + +#if !defined(_WIN32) + static void extract_archive(const VcpkgPaths& paths, const fs::path& archive, const fs::path& to_path) + { + Files::Filesystem& fs = paths.get_filesystem(); + const fs::path to_path_partial = to_path.u8string() + ".partial"; + + std::error_code ec; + fs.remove_all(to_path_partial, ec); + fs.create_directories(to_path_partial, ec); + + const auto ext = archive.extension(); + if (ext == ".gz" && ext.extension() != ".tar") + { + const auto code = System::cmd_execute( + Strings::format(R"(cd '%s' && tar xzf '%s')", to_path_partial.u8string(), archive.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "tar failed while extracting %s", archive.u8string()); + } + else if (ext == ".zip") + { + const auto code = System::cmd_execute( + Strings::format(R"(cd '%s' && unzip -qqo '%s')", to_path_partial.u8string(), archive.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "unzip failed while extracting %s", archive.u8string()); + } + else + { + Checks::exit_with_message(VCPKG_LINE_INFO, "Unexpected archive extension: %s", ext.u8string()); + } + + fs.rename(to_path_partial, to_path); + } + + static void verify_hash(const VcpkgPaths& paths, + const std::string& url, + const fs::path& path, + const std::string& sha512) + { + const std::string actual_hash = Hash::get_file_hash(paths, path, "SHA512"); + Checks::check_exit(VCPKG_LINE_INFO, + sha512 == actual_hash, + "File does not have the expected hash:\n" + " url : [ %s ]\n" + " File path : [ %s ]\n" + " Expected hash : [ %s ]\n" + " Actual hash : [ %s ]\n", + url, + path.u8string(), + sha512, + actual_hash); + } + + static void download_file(const VcpkgPaths& paths, + const std::string& url, + const fs::path& download_path, + const std::string& sha512) + { + Files::Filesystem& fs = paths.get_filesystem(); + const std::string download_path_part = download_path.u8string() + ".part"; + std::error_code ec; + fs.remove(download_path_part, ec); + const auto code = System::cmd_execute( + Strings::format(R"(curl -L '%s' --create-dirs --output '%s')", url, download_path_part)); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "Could not download %s", url); + + verify_hash(paths, url, download_path_part, sha512); + fs.rename(download_path_part, download_path); + } + +#endif + static fs::path fetch_tool(const VcpkgPaths& paths, const std::string& tool_name, const ToolData& tool_data) + { + const std::array<int, 3>& version = tool_data.version; + const std::string version_as_string = Strings::format("%d.%d.%d", version[0], version[1], version[2]); + Checks::check_exit(VCPKG_LINE_INFO, + !tool_data.url.empty(), + "A suitable version of %s was not found (required v%s) and unable to automatically " + "download a portable one. Please install a newer version of git.", + tool_name, + version_as_string); + System::println("A suitable version of %s was not found (required v%s). Downloading portable %s v%s...", + tool_name, + version_as_string, + tool_name, + version_as_string); +#if defined(_WIN32) + const fs::path script = paths.scripts / "fetchtool.ps1"; + const std::string title = Strings::format( + "Fetching %s version %s (No sufficient installed version was found)", tool_name, version_as_string); + const System::PowershellParameter tool_param("tool", tool_name); + const std::string output = System::powershell_execute_and_capture_output(title, script, {tool_param}); + + const std::vector<std::string> tool_path = keep_data_lines(output); + Checks::check_exit(VCPKG_LINE_INFO, tool_path.size() == 1, "Expected tool path, but got %s", output); + + const fs::path actual_downloaded_path = Strings::trim(std::string{tool_path.at(0)}); + const fs::path& expected_downloaded_path = tool_data.exe_path; + std::error_code ec; + const auto eq = fs::stdfs::equivalent(expected_downloaded_path, actual_downloaded_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + eq && !ec, + "Expected tool downloaded path to be %s, but was %s", + expected_downloaded_path.u8string(), + actual_downloaded_path.u8string()); + return actual_downloaded_path; +#else + const auto& fs = paths.get_filesystem(); + if (!fs.exists(tool_data.download_path)) + { + System::println("Downloading %s...", tool_name); + download_file(paths, tool_data.url, tool_data.download_path, tool_data.sha512); + System::println("Downloading %s... done.", tool_name); + } + else + { + verify_hash(paths, tool_data.url, tool_data.download_path, tool_data.sha512); + } + + System::println("Extracting %s...", tool_name); + extract_archive(paths, tool_data.download_path, tool_data.tool_dir_path); + System::println("Extracting %s... done.", tool_name); + + Checks::check_exit(VCPKG_LINE_INFO, + fs.exists(tool_data.exe_path), + "Expected %s to exist after extracting", + tool_data.exe_path); + + return tool_data.exe_path; +#endif + } + + static fs::path get_cmake_path(const VcpkgPaths& paths) + { + std::vector<fs::path> candidate_paths; +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "cmake"); + candidate_paths.push_back(TOOL_DATA.exe_path); +#else + static const ToolData TOOL_DATA = ToolData{{3, 5, 1}, ""}; +#endif + static const std::string VERSION_CHECK_ARGUMENTS = "--version"; + + const std::vector<fs::path> from_path = Files::find_from_PATH("cmake"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); + + const Optional<fs::path> path = + find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "cmake", TOOL_DATA); + } + + static fs::path get_7za_path(const VcpkgPaths& paths) + { +#if defined(_WIN32) + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "7zip"); + if (!paths.get_filesystem().exists(TOOL_DATA.exe_path)) + { + return fetch_tool(paths, "7zip", TOOL_DATA); + } + return TOOL_DATA.exe_path; +#else + Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot download 7zip for non-Windows platforms."); +#endif + } + + static fs::path get_ninja_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "ninja"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + const std::vector<fs::path> from_path = Files::find_from_PATH("ninja"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + auto path = find_if_has_equal_or_greater_version(candidate_paths, "--version", TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "ninja", TOOL_DATA); + } + + static fs::path get_nuget_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "nuget"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + const std::vector<fs::path> from_path = Files::find_from_PATH("nuget"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + auto path = find_if_has_equal_or_greater_version(candidate_paths, "", TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "nuget", TOOL_DATA); + } + + static fs::path get_git_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "git"); + static const std::string VERSION_CHECK_ARGUMENTS = "--version"; + + std::vector<fs::path> candidate_paths; +#if defined(_WIN32) + candidate_paths.push_back(TOOL_DATA.exe_path); +#endif + const std::vector<fs::path> from_path = Files::find_from_PATH("git"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); + + const Optional<fs::path> path = + find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "git", TOOL_DATA); + } + + static fs::path get_ifw_installerbase_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "installerbase"); + + static const std::string VERSION_CHECK_ARGUMENTS = "--framework-version"; + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + // TODO: Uncomment later + // const std::vector<fs::path> from_path = Files::find_from_PATH("installerbase"); + // candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / + // "Tools" / "QtInstallerFramework" / "3.1" / "bin" / "installerbase.exe"); + // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / + // "QtIFW-3.1.0" / "bin" / "installerbase.exe"); + + const Optional<fs::path> path = + find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "installerbase", TOOL_DATA); + } + + struct VisualStudioInstance + { + fs::path root_path; + std::string version; + std::string release_type; + std::string preference_weight; // Mostly unused, just for verification that order is as intended + + std::string major_version() const { return version.substr(0, 2); } + }; + + static std::vector<VisualStudioInstance> get_visual_studio_instances(const VcpkgPaths& paths) + { + const fs::path script = paths.scripts / "findVisualStudioInstallationInstances.ps1"; + const std::string output = + System::powershell_execute_and_capture_output("Detecting Visual Studio instances", script); + + const std::vector<std::string> instances_as_strings = keep_data_lines(output); + Checks::check_exit(VCPKG_LINE_INFO, + !instances_as_strings.empty(), + "Could not detect any Visual Studio instances.\n" + "Powershell script:\n" + " %s\n" + "returned:\n" + "%s", + script.generic_string(), + output); + + std::vector<VisualStudioInstance> instances; + for (const std::string& instance_as_string : instances_as_strings) + { + const std::vector<std::string> split = Strings::split(instance_as_string, "::"); + Checks::check_exit(VCPKG_LINE_INFO, + split.size() == 4, + "Invalid Visual Studio instance format.\n" + "Expected: PreferenceWeight::ReleaseType::Version::PathToVisualStudio\n" + "Actual : %s\n", + instance_as_string); + instances.push_back({split.at(3), split.at(2), split.at(1), split.at(0)}); + } + + return instances; + } + + std::vector<Toolset> find_toolset_instances(const VcpkgPaths& paths) + { + using CPU = System::CPUArchitecture; + + const auto& fs = paths.get_filesystem(); + + // Note: this will contain a mix of vcvarsall.bat locations and dumpbin.exe locations. + std::vector<fs::path> paths_examined; + + std::vector<Toolset> found_toolsets; + std::vector<Toolset> excluded_toolsets; + + const std::vector<VisualStudioInstance> vs_instances = get_visual_studio_instances(paths); + const bool v140_is_available = Util::find_if(vs_instances, [&](const VisualStudioInstance& vs_instance) { + return vs_instance.major_version() == "14"; + }) != vs_instances.cend(); + + for (const VisualStudioInstance& vs_instance : vs_instances) + { + const std::string major_version = vs_instance.major_version(); + if (major_version == "15") + { + const fs::path vc_dir = vs_instance.root_path / "VC"; + + // Skip any instances that do not have vcvarsall. + const fs::path vcvarsall_dir = vc_dir / "Auxiliary" / "Build"; + const fs::path vcvarsall_bat = vcvarsall_dir / "vcvarsall.bat"; + paths_examined.push_back(vcvarsall_bat); + if (!fs.exists(vcvarsall_bat)) continue; + + // Get all supported architectures + std::vector<ToolsetArchOption> supported_architectures; + if (fs.exists(vcvarsall_dir / "vcvars32.bat")) + supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); + if (fs.exists(vcvarsall_dir / "vcvars64.bat")) + supported_architectures.push_back({"amd64", CPU::X64, CPU::X64}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_amd64.bat")) + supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_arm.bat")) + supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_arm64.bat")) + supported_architectures.push_back({"x86_arm64", CPU::X86, CPU::ARM64}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_x86.bat")) + supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm.bat")) + supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm64.bat")) + supported_architectures.push_back({"amd64_arm64", CPU::X64, CPU::ARM64}); + + // Locate the "best" MSVC toolchain version + const fs::path msvc_path = vc_dir / "Tools" / "MSVC"; + std::vector<fs::path> msvc_subdirectories = fs.get_files_non_recursive(msvc_path); + Util::unstable_keep_if(msvc_subdirectories, + [&fs](const fs::path& path) { return fs.is_directory(path); }); + + // Sort them so that latest comes first + std::sort( + msvc_subdirectories.begin(), + msvc_subdirectories.end(), + [](const fs::path& left, const fs::path& right) { return left.filename() > right.filename(); }); + + for (const fs::path& subdir : msvc_subdirectories) + { + const fs::path dumpbin_path = subdir / "bin" / "HostX86" / "x86" / "dumpbin.exe"; + paths_examined.push_back(dumpbin_path); + if (fs.exists(dumpbin_path)) + { + const Toolset v141toolset = Toolset{ + vs_instance.root_path, dumpbin_path, vcvarsall_bat, {}, V_141, supported_architectures}; + + auto english_language_pack = dumpbin_path.parent_path() / "1033"; + + if (!fs.exists(english_language_pack)) + { + excluded_toolsets.push_back(v141toolset); + break; + } + + found_toolsets.push_back(v141toolset); + + if (v140_is_available) + { + const Toolset v140toolset = Toolset{vs_instance.root_path, + dumpbin_path, + vcvarsall_bat, + {"-vcvars_ver=14.0"}, + V_140, + supported_architectures}; + found_toolsets.push_back(v140toolset); + } + + break; + } + } + + continue; + } + + if (major_version == "14" || major_version == "12") + { + const fs::path vcvarsall_bat = vs_instance.root_path / "VC" / "vcvarsall.bat"; + + paths_examined.push_back(vcvarsall_bat); + if (fs.exists(vcvarsall_bat)) + { + const fs::path vs_dumpbin_exe = vs_instance.root_path / "VC" / "bin" / "dumpbin.exe"; + paths_examined.push_back(vs_dumpbin_exe); + + const fs::path vs_bin_dir = vcvarsall_bat.parent_path() / "bin"; + std::vector<ToolsetArchOption> supported_architectures; + if (fs.exists(vs_bin_dir / "vcvars32.bat")) + supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); + if (fs.exists(vs_bin_dir / "amd64\\vcvars64.bat")) + supported_architectures.push_back({"x64", CPU::X64, CPU::X64}); + if (fs.exists(vs_bin_dir / "x86_amd64\\vcvarsx86_amd64.bat")) + supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); + if (fs.exists(vs_bin_dir / "x86_arm\\vcvarsx86_arm.bat")) + supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); + if (fs.exists(vs_bin_dir / "amd64_x86\\vcvarsamd64_x86.bat")) + supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); + if (fs.exists(vs_bin_dir / "amd64_arm\\vcvarsamd64_arm.bat")) + supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); + + if (fs.exists(vs_dumpbin_exe)) + { + const Toolset toolset = {vs_instance.root_path, + vs_dumpbin_exe, + vcvarsall_bat, + {}, + major_version == "14" ? V_140 : V_120, + supported_architectures}; + + auto english_language_pack = vs_dumpbin_exe.parent_path() / "1033"; + + if (!fs.exists(english_language_pack)) + { + excluded_toolsets.push_back(toolset); + break; + } + + found_toolsets.push_back(toolset); + } + } + } + } + + if (!excluded_toolsets.empty()) + { + System::println( + System::Color::warning, + "Warning: The following VS instances are excluded because the English language pack is unavailable."); + for (const Toolset& toolset : excluded_toolsets) + { + System::println(" %s", toolset.visual_studio_root_path.u8string()); + } + System::println(System::Color::warning, "Please install the English language pack."); + } + + if (found_toolsets.empty()) + { + System::println(System::Color::error, "Could not locate a complete toolset."); + System::println("The following paths were examined:"); + for (const fs::path& path : paths_examined) + { + System::println(" %s", path.u8string()); + } + Checks::exit_fail(VCPKG_LINE_INFO); + } + + return found_toolsets; + } + + fs::path get_tool_path(const VcpkgPaths& paths, const std::string& tool) + { + // First deal with specially handled tools. + // For these we may look in locations like Program Files, the PATH etc as well as the auto-downloaded location. + if (tool == Tools::SEVEN_ZIP) return get_7za_path(paths); + if (tool == Tools::CMAKE) return get_cmake_path(paths); + if (tool == Tools::GIT) return get_git_path(paths); + if (tool == Tools::NINJA) return get_ninja_path(paths); + if (tool == Tools::NUGET) return get_nuget_path(paths); + if (tool == Tools::IFW_INSTALLER_BASE) return get_ifw_installerbase_path(paths); + if (tool == Tools::IFW_BINARYCREATOR) + return get_ifw_installerbase_path(paths).parent_path() / "binarycreator.exe"; + if (tool == Tools::IFW_REPOGEN) return get_ifw_installerbase_path(paths).parent_path() / "repogen.exe"; + + // For other tools, we simply always auto-download them. + const ToolData tool_data = parse_tool_data_from_xml(paths, tool); + if (paths.get_filesystem().exists(tool_data.exe_path)) + { + return tool_data.exe_path; + } + return fetch_tool(paths, tool, tool_data); + } + + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be tool name\n%s", Help::create_example_string("fetch cmake")), + 1, + 1, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + const std::string tool = args.command_arguments[0]; + const fs::path tool_path = get_tool_path(paths, tool); + System::println(tool_path.u8string()); + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.hash.cpp b/toolsrc/src/vcpkg/commands.hash.cpp index bbbbed036..1f709f87b 100644 --- a/toolsrc/src/vcpkg/commands.hash.cpp +++ b/toolsrc/src/vcpkg/commands.hash.cpp @@ -1,5 +1,7 @@ #include "pch.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/base/strings.h> #include <vcpkg/base/system.h> #include <vcpkg/base/util.h> #include <vcpkg/commands.h> @@ -7,32 +9,221 @@ namespace vcpkg::Commands::Hash { - std::string get_file_hash(fs::path const& cmake_exe_path, fs::path const& path, std::string const& hash_type) + static void verify_has_only_allowed_chars(const std::string& s) { - const std::string cmd_line = Strings::format( - R"("%s" -E %ssum "%s")", - cmake_exe_path.u8string(), - Strings::ascii_to_lowercase(hash_type), - path.u8string()); + static const std::regex ALLOWED_CHARS{"^[a-zA-Z0-9-]*$"}; + Checks::check_exit(VCPKG_LINE_INFO, + std::regex_match(s, ALLOWED_CHARS), + "Only alphanumeric chars and dashes are currently allowed. String was:\n" + " % s", + s); + } +} - const auto ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", cmd_line); +#if defined(_WIN32) +#include <bcrypt.h> + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +namespace vcpkg::Commands::Hash +{ + namespace + { + std::string to_hex(const unsigned char* string, const size_t bytes) + { + static constexpr char HEX_MAP[] = "0123456789abcdef"; + + std::string output; + output.resize(2 * bytes); + + size_t current_char = 0; + for (size_t i = 0; i < bytes; i++) + { + // high + output[current_char] = HEX_MAP[(string[i] & 0xF0) >> 4]; + ++current_char; + // low + output[current_char] = HEX_MAP[(string[i] & 0x0F)]; + ++current_char; + } + + return output; + } + + class BCryptHasher + { + struct BCryptAlgorithmHandle : Util::ResourceBase + { + BCRYPT_ALG_HANDLE handle = nullptr; - std::string const& output = ec_data.output; + ~BCryptAlgorithmHandle() + { + if (handle) BCryptCloseAlgorithmProvider(handle, 0); + } + }; - const auto start = output.find_first_of(' '); + struct BCryptHashHandle : Util::ResourceBase + { + BCRYPT_HASH_HANDLE handle = nullptr; + + ~BCryptHashHandle() + { + if (handle) BCryptDestroyHash(handle); + } + }; + + static void initialize_hash_handle(BCryptHashHandle& hash_handle, + const BCryptAlgorithmHandle& algorithm_handle) + { + const NTSTATUS error_code = + BCryptCreateHash(algorithm_handle.handle, &hash_handle.handle, nullptr, 0, nullptr, 0, 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to initialize the hasher"); + } + + static void hash_data(BCryptHashHandle& hash_handle, const unsigned char* buffer, const size_t& data_size) + { + const NTSTATUS error_code = BCryptHashData( + hash_handle.handle, const_cast<unsigned char*>(buffer), static_cast<ULONG>(data_size), 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to hash data"); + } + + static std::string finalize_hash_handle(const BCryptHashHandle& hash_handle, const ULONG length_in_bytes) + { + std::unique_ptr<unsigned char[]> hash_buffer = std::make_unique<UCHAR[]>(length_in_bytes); + const NTSTATUS error_code = BCryptFinishHash(hash_handle.handle, hash_buffer.get(), length_in_bytes, 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to finalize the hash"); + return to_hex(hash_buffer.get(), length_in_bytes); + } + + public: + explicit BCryptHasher(const std::string& hash_type) + { + NTSTATUS error_code = + BCryptOpenAlgorithmProvider(&this->algorithm_handle.handle, + Strings::to_utf16(Strings::ascii_to_uppercase(hash_type)).c_str(), + nullptr, + 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to open the algorithm provider"); + + DWORD hash_buffer_bytes; + DWORD cb_data; + error_code = BCryptGetProperty(this->algorithm_handle.handle, + BCRYPT_HASH_LENGTH, + reinterpret_cast<PUCHAR>(&hash_buffer_bytes), + sizeof(DWORD), + &cb_data, + 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to get hash length"); + this->length_in_bytes = hash_buffer_bytes; + } + + std::string hash_file(const fs::path& path) const + { + BCryptHashHandle hash_handle; + initialize_hash_handle(hash_handle, this->algorithm_handle); + + FILE* file = nullptr; + const auto ec = _wfopen_s(&file, path.c_str(), L"rb"); + Checks::check_exit(VCPKG_LINE_INFO, ec == 0, "Failed to open file: %s", path.u8string()); + if (file != nullptr) + { + unsigned char buffer[4096]; + while (const auto actual_size = fread(buffer, 1, sizeof(buffer), file)) + { + hash_data(hash_handle, buffer, actual_size); + } + fclose(file); + } + + return finalize_hash_handle(hash_handle, length_in_bytes); + } + + std::string hash_string(const std::string& s) const + { + BCryptHashHandle hash_handle; + initialize_hash_handle(hash_handle, this->algorithm_handle); + hash_data(hash_handle, reinterpret_cast<const unsigned char*>(s.c_str()), s.size()); + return finalize_hash_handle(hash_handle, length_in_bytes); + } + + private: + BCryptAlgorithmHandle algorithm_handle; + ULONG length_in_bytes; + }; + } + + std::string get_file_hash(const VcpkgPaths& paths, const fs::path& path, const std::string& hash_type) + { Checks::check_exit( - VCPKG_LINE_INFO, start != std::string::npos, "Unexpected output format from command: %s", cmd_line); + VCPKG_LINE_INFO, paths.get_filesystem().exists(path), "File %s does not exist", path.u8string()); + return BCryptHasher{hash_type}.hash_file(path); + } - const auto end = output.find_first_of("\r\n", start + 1); + std::string get_string_hash(const std::string& s, const std::string& hash_type) + { + verify_has_only_allowed_chars(s); + return BCryptHasher{hash_type}.hash_string(s); + } +} + +#else +namespace vcpkg::Commands::Hash +{ + static std::string get_digest_size(const std::string& hash_type) + { + if (!Strings::case_insensitive_ascii_starts_with(hash_type, "SHA")) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "shasum only supports SHA hashes, but %s was provided", hash_type); + } + + return hash_type.substr(3, hash_type.length() - 3); + } + + static std::string run_shasum_and_post_process(const std::string& cmd_line) + { + const auto ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, + ec_data.exit_code == 0, + "Failed to run:\n" + " %s", + cmd_line); + + std::vector<std::string> split = Strings::split(ec_data.output, " "); + Checks::check_exit(VCPKG_LINE_INFO, + split.size() == 3, + "Expected output of the form [hash filename\n] (3 tokens), but got\n" + "[%s] (%s tokens)", + ec_data.output, + std::to_string(split.size())); + + return split[0]; + } + + std::string get_file_hash(const VcpkgPaths& paths, const fs::path& path, const std::string& hash_type) + { + const std::string digest_size = get_digest_size(hash_type); Checks::check_exit( - VCPKG_LINE_INFO, end != std::string::npos, "Unexpected output format from command: %s", cmd_line); + VCPKG_LINE_INFO, paths.get_filesystem().exists(path), "File %s does not exist", path.u8string()); + const std::string cmd_line = Strings::format(R"(shasum -a %s "%s")", digest_size, path.u8string()); + return run_shasum_and_post_process(cmd_line); + } + + std::string get_string_hash(const std::string& s, const std::string& hash_type) + { + const std::string digest_size = get_digest_size(hash_type); + verify_has_only_allowed_chars(s); - auto hash = output.substr(0, start); - Util::erase_remove_if(hash, isspace); - return hash; + const std::string cmd_line = Strings::format(R"(echo -n "%s" | shasum -a %s)", s, digest_size); + return run_shasum_and_post_process(cmd_line); } +} +#endif +namespace vcpkg::Commands::Hash +{ const CommandStructure COMMAND_STRUCTURE = { Strings::format("The argument should be a file path\n%s", Help::create_example_string("hash boost_1_62_0.tar.bz2")), @@ -44,19 +235,12 @@ namespace vcpkg::Commands::Hash void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); - - if (args.command_arguments.size() == 1) - { - auto hash = get_file_hash(paths.get_cmake_exe(), args.command_arguments[0], "SHA512"); - System::println(hash); - } - if (args.command_arguments.size() == 2) - { - auto hash = get_file_hash(paths.get_cmake_exe(), args.command_arguments[0], args.command_arguments[1]); - System::println(hash); - } + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + const fs::path file_to_hash = args.command_arguments[0]; + const std::string algorithm = args.command_arguments.size() == 2 ? args.command_arguments[1] : "SHA512"; + const std::string hash = get_file_hash(paths, file_to_hash, algorithm); + System::println(hash); Checks::exit_success(VCPKG_LINE_INFO); } } diff --git a/toolsrc/src/vcpkg/commands.import.cpp b/toolsrc/src/vcpkg/commands.import.cpp index 24394207b..4b595697a 100644 --- a/toolsrc/src/vcpkg/commands.import.cpp +++ b/toolsrc/src/vcpkg/commands.import.cpp @@ -103,7 +103,7 @@ namespace vcpkg::Commands::Import void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const fs::path control_file_path(args.command_arguments[0]); const fs::path include_directory(args.command_arguments[1]); diff --git a/toolsrc/src/vcpkg/commands.integrate.cpp b/toolsrc/src/vcpkg/commands.integrate.cpp index 460e99b88..8897ea138 100644 --- a/toolsrc/src/vcpkg/commands.integrate.cpp +++ b/toolsrc/src/vcpkg/commands.integrate.cpp @@ -5,9 +5,11 @@ #include <vcpkg/base/system.h> #include <vcpkg/base/util.h> #include <vcpkg/commands.h> +#include <vcpkg/userconfig.h> namespace vcpkg::Commands::Integrate { +#if defined(_WIN32) static std::string create_appdata_targets_shortcut(const std::string& target_path) noexcept { return Strings::format(R"###( @@ -18,7 +20,9 @@ namespace vcpkg::Commands::Integrate target_path, target_path); } +#endif +#if defined(_WIN32) static std::string create_system_targets_shortcut() noexcept { return R"###( @@ -31,7 +35,9 @@ namespace vcpkg::Commands::Integrate </Project> )###"; } +#endif +#if defined(_WIN32) static std::string create_nuget_targets_file_contents(const fs::path& msbuild_vcpkg_targets_file) noexcept { const std::string as_string = msbuild_vcpkg_targets_file.string(); @@ -47,7 +53,9 @@ namespace vcpkg::Commands::Integrate as_string, as_string); } +#endif +#if defined(_WIN32) static std::string create_nuget_props_file_contents() noexcept { return R"###( @@ -58,7 +66,9 @@ namespace vcpkg::Commands::Integrate </Project> )###"; } +#endif +#if defined(_WIN32) static std::string get_nuget_id(const fs::path& vcpkg_root_dir) { std::string dir_id = vcpkg_root_dir.generic_string(); @@ -71,7 +81,9 @@ namespace vcpkg::Commands::Integrate const std::string nuget_id = "vcpkg." + dir_id; return nuget_id; } +#endif +#if defined(_WIN32) static std::string create_nuspec_file_contents(const fs::path& vcpkg_root_dir, const std::string& nuget_id, const std::string& nupkg_version) @@ -98,17 +110,18 @@ namespace vcpkg::Commands::Integrate content = Strings::replace_all(std::move(content), "@VERSION@", nupkg_version); return content; } +#endif +#if defined(_WIN32) enum class ElevationPromptChoice { YES, NO }; -#if defined(_WIN32) static ElevationPromptChoice elevated_cmd_execute(const std::string& param) { - SHELLEXECUTEINFOW sh_ex_info = {0}; + SHELLEXECUTEINFOW sh_ex_info{}; sh_ex_info.cbSize = sizeof(sh_ex_info); sh_ex_info.fMask = SEE_MASK_NOCLOSEPROCESS; sh_ex_info.hwnd = nullptr; @@ -144,20 +157,20 @@ namespace vcpkg::Commands::Integrate } #endif + static fs::path get_path_txt_path() { return get_user_dir() / "vcpkg.path.txt"; } + #if defined(_WIN32) - static void integrate_install(const VcpkgPaths& paths) + static void integrate_install_msbuild14(Files::Filesystem& fs, const fs::path& tmp_dir) { static const std::array<fs::path, 2> OLD_SYSTEM_TARGET_FILES = { - System::get_program_files_32_bit() / + System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO) / "MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.nuget.targets", - System::get_program_files_32_bit() / + System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO) / "MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.system.targets"}; static const fs::path SYSTEM_WIDE_TARGETS_FILE = - System::get_program_files_32_bit() / + System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO) / "MSBuild/Microsoft.Cpp/v4.0/V140/ImportBefore/Default/vcpkg.system.props"; - auto& fs = paths.get_filesystem(); - // TODO: This block of code should eventually be removed for (auto&& old_system_wide_targets_file : OLD_SYSTEM_TARGET_FILES) { @@ -176,12 +189,6 @@ namespace vcpkg::Commands::Integrate } } } - - std::error_code ec; - const fs::path tmp_dir = paths.buildsystems / "tmp"; - fs.create_directory(paths.buildsystems, ec); - fs.create_directory(tmp_dir, ec); - bool should_install_system = true; const Expected<std::string> system_wide_file_contents = fs.read_contents(SYSTEM_WIDE_TARGETS_FILE); static const std::regex RE(R"###(<!-- version (\d+) -->)###"); @@ -220,24 +227,52 @@ namespace vcpkg::Commands::Integrate "Error: failed to copy targets file to %s", SYSTEM_WIDE_TARGETS_FILE.string()); } + } +#endif + + static void integrate_install(const VcpkgPaths& paths) + { + auto& fs = paths.get_filesystem(); + +#if defined(_WIN32) + { + std::error_code ec; + const fs::path tmp_dir = paths.buildsystems / "tmp"; + fs.create_directory(paths.buildsystems, ec); + fs.create_directory(tmp_dir, ec); + + integrate_install_msbuild14(fs, tmp_dir); + + const fs::path appdata_src_path = tmp_dir / "vcpkg.user.targets"; + fs.write_contents(appdata_src_path, + create_appdata_targets_shortcut(paths.buildsystems_msbuild_targets.string())); + auto appdata_dst_path = get_appdata_targets_path(); - const fs::path appdata_src_path = tmp_dir / "vcpkg.user.targets"; - fs.write_contents(appdata_src_path, - create_appdata_targets_shortcut(paths.buildsystems_msbuild_targets.string())); - auto appdata_dst_path = get_appdata_targets_path(); + const auto rc = fs.copy_file(appdata_src_path, appdata_dst_path, fs::copy_options::overwrite_existing, ec); - const auto rc = fs.copy_file(appdata_src_path, appdata_dst_path, fs::copy_options::overwrite_existing, ec); + if (!rc || ec) + { + System::println(System::Color::error, + "Error: Failed to copy file: %s -> %s", + appdata_src_path.string(), + appdata_dst_path.string()); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } +#endif - if (!rc || ec) + const auto pathtxt = get_path_txt_path(); + std::error_code ec; + fs.write_contents(pathtxt, paths.root.generic_u8string(), ec); + if (ec) { - System::println(System::Color::error, - "Error: Failed to copy file: %s -> %s", - appdata_src_path.string(), - appdata_dst_path.string()); + System::println(System::Color::error, "Error: Failed to write file: %s", pathtxt.string()); Checks::exit_fail(VCPKG_LINE_INFO); } + System::println(System::Color::success, "Applied user-wide integration for this vcpkg root."); const fs::path cmake_toolchain = paths.buildsystems / "vcpkg.cmake"; +#if defined(_WIN32) System::println( R"( All MSBuild C++ projects can now #include any installed libraries. @@ -246,17 +281,29 @@ Installing new libraries will make them instantly available. CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=%s")", cmake_toolchain.generic_string()); +#else + System::println( + R"( +CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=%s")", + cmake_toolchain.generic_string()); +#endif Checks::exit_success(VCPKG_LINE_INFO); } static void integrate_remove(Files::Filesystem& fs) { + std::error_code ec; + bool was_deleted = false; + +#if defined(_WIN32) const fs::path path = get_appdata_targets_path(); - std::error_code ec; - const bool was_deleted = fs.remove(path, ec); + was_deleted |= fs.remove(path, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Error: Unable to remove user-wide integration: %s", ec.message()); +#endif + was_deleted |= fs.remove(get_path_txt_path(), ec); Checks::check_exit(VCPKG_LINE_INFO, !ec, "Error: Unable to remove user-wide integration: %s", ec.message()); if (was_deleted) @@ -270,13 +317,13 @@ CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=%s")", Checks::exit_success(VCPKG_LINE_INFO); } -#endif +#if defined(WIN32) static void integrate_project(const VcpkgPaths& paths) { auto& fs = paths.get_filesystem(); - const fs::path& nuget_exe = paths.get_nuget_exe(); + const fs::path& nuget_exe = paths.get_tool_exe(Tools::NUGET); const fs::path& buildsystems_dir = paths.buildsystems; const fs::path tmp_dir = buildsystems_dir / "tmp"; @@ -319,13 +366,20 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console Checks::exit_success(VCPKG_LINE_INFO); } +#endif +#if defined(_WIN32) const char* const INTEGRATE_COMMAND_HELPSTRING = " vcpkg integrate install Make installed packages available user-wide. Requires admin privileges on " "first use\n" " vcpkg integrate remove Remove user-wide integration\n" " vcpkg integrate project Generate a referencing nuget package for individual VS project use\n" " vcpkg integrate powershell Enable PowerShell Tab-Completion\n"; +#else + const char* const INTEGRATE_COMMAND_HELPSTRING = + " vcpkg integrate install Make installed packages available user-wide.\n" + " vcpkg integrate remove Remove user-wide integration\n"; +#endif namespace Subcommand { @@ -352,9 +406,8 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); -#if defined(_WIN32) if (args.command_arguments[0] == Subcommand::INSTALL) { return integrate_install(paths); @@ -363,6 +416,7 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console { return integrate_remove(paths.get_filesystem()); } +#if defined(_WIN32) if (args.command_arguments[0] == Subcommand::PROJECT) { return integrate_project(paths); diff --git a/toolsrc/src/vcpkg/commands.list.cpp b/toolsrc/src/vcpkg/commands.list.cpp index a5efd5442..1bfbc4247 100644 --- a/toolsrc/src/vcpkg/commands.list.cpp +++ b/toolsrc/src/vcpkg/commands.list.cpp @@ -10,7 +10,7 @@ namespace vcpkg::Commands::List static constexpr StringLiteral OPTION_FULLDESC = "--x-full-desc"; // TODO: This should find a better home, eventually - static void do_print(const StatusParagraph& pgh, bool full_desc) + static void do_print(const StatusParagraph& pgh, const bool full_desc) { if (full_desc) { @@ -44,14 +44,19 @@ namespace vcpkg::Commands::List const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); const StatusParagraphs status_paragraphs = database_load_check(paths); - std::vector<StatusParagraph*> installed_packages = get_installed_ports(status_paragraphs); + auto installed_ipv = get_installed_ports(status_paragraphs); - if (installed_packages.empty()) + if (installed_ipv.empty()) { System::println("No packages are installed. Did you mean `search`?"); Checks::exit_success(VCPKG_LINE_INFO); } + auto installed_packages = Util::fmap(installed_ipv, [](const InstalledPackageView& ipv) { return ipv.core; }); + auto installed_features = + Util::fmap_flatten(installed_ipv, [](const InstalledPackageView& ipv) { return ipv.features; }); + installed_packages.insert(installed_packages.end(), installed_features.begin(), installed_features.end()); + std::sort(installed_packages.begin(), installed_packages.end(), [](const StatusParagraph* lhs, const StatusParagraph* rhs) -> bool { diff --git a/toolsrc/src/vcpkg/commands.owns.cpp b/toolsrc/src/vcpkg/commands.owns.cpp index 52249187b..ee9584651 100644 --- a/toolsrc/src/vcpkg/commands.owns.cpp +++ b/toolsrc/src/vcpkg/commands.owns.cpp @@ -34,7 +34,7 @@ namespace vcpkg::Commands::Owns void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const StatusParagraphs status_db = database_load_check(paths); search_file(paths, args.command_arguments[0], status_db); diff --git a/toolsrc/src/vcpkg/commands.portsdiff.cpp b/toolsrc/src/vcpkg/commands.portsdiff.cpp index dba04ce5b..2d2b4bd5f 100644 --- a/toolsrc/src/vcpkg/commands.portsdiff.cpp +++ b/toolsrc/src/vcpkg/commands.portsdiff.cpp @@ -3,6 +3,7 @@ #include <vcpkg/commands.h> #include <vcpkg/help.h> #include <vcpkg/paragraphs.h> +#include <vcpkg/versiont.h> #include <vcpkg/base/sortedvector.h> #include <vcpkg/base/system.h> @@ -79,7 +80,7 @@ namespace vcpkg::Commands::PortsDiff { std::error_code ec; auto& fs = paths.get_filesystem(); - const fs::path& git_exe = paths.get_git_exe(); + const fs::path& git_exe = paths.get_tool_exe(Tools::GIT); const fs::path dot_git_dir = paths.root / ".git"; const std::string ports_dir_name_as_string = paths.ports.filename().u8string(); const fs::path temp_checkout_path = @@ -128,9 +129,9 @@ namespace vcpkg::Commands::PortsDiff void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); - const fs::path& git_exe = paths.get_git_exe(); + const fs::path& git_exe = paths.get_tool_exe(Tools::GIT); const std::string git_commit_id_for_previous_snapshot = args.command_arguments.at(0); const std::string git_commit_id_for_current_snapshot = diff --git a/toolsrc/src/vcpkg/commands.upgrade.cpp b/toolsrc/src/vcpkg/commands.upgrade.cpp index c5b48f2a2..a902ddeaf 100644 --- a/toolsrc/src/vcpkg/commands.upgrade.cpp +++ b/toolsrc/src/vcpkg/commands.upgrade.cpp @@ -144,7 +144,8 @@ namespace vcpkg::Commands::Upgrade const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, Build::AllowDownloads::YES, Build::CleanBuildtrees::NO, - Build::CleanPackages::NO}; + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN}; // Set build settings for all install actions for (auto&& action : plan) diff --git a/toolsrc/src/vcpkg/commands.version.cpp b/toolsrc/src/vcpkg/commands.version.cpp index ffc5d2c8f..2ad91b57d 100644 --- a/toolsrc/src/vcpkg/commands.version.cpp +++ b/toolsrc/src/vcpkg/commands.version.cpp @@ -83,7 +83,7 @@ namespace vcpkg::Commands::Version void perform_and_exit(const VcpkgCmdArguments& args) { - args.parse_arguments(COMMAND_STRUCTURE); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); System::println("Vcpkg package management program version %s\n" "\n" diff --git a/toolsrc/src/vcpkg/dependencies.cpp b/toolsrc/src/vcpkg/dependencies.cpp index 1d017a8d3..8fb35b0da 100644 --- a/toolsrc/src/vcpkg/dependencies.cpp +++ b/toolsrc/src/vcpkg/dependencies.cpp @@ -13,11 +13,17 @@ namespace vcpkg::Dependencies { - struct FeatureNodeEdges + struct ClusterInstalled { - std::vector<FeatureSpec> remove_edges; - std::vector<FeatureSpec> build_edges; - bool plus = false; + InstalledPackageView ipv; + std::set<PackageSpec> remove_edges; + std::set<std::string> original_features; + }; + + struct ClusterSource + { + const SourceControlFile* scf = nullptr; + std::unordered_map<std::string, std::vector<FeatureSpec>> build_edges; }; /// <summary> @@ -25,14 +31,15 @@ namespace vcpkg::Dependencies /// </summary> struct Cluster : Util::MoveOnlyBase { - InstalledPackageView installed_package; - - Optional<const SourceControlFile*> source_control_file; PackageSpec spec; - std::unordered_map<std::string, FeatureNodeEdges> edges_by_feature; + + Optional<ClusterInstalled> installed; + Optional<ClusterSource> source; + + // Note: this map can contain "special" strings such as "" and "*" + std::unordered_map<std::string, bool> plus; std::set<std::string> to_install_features; - std::set<std::string> original_features; - bool will_remove = false; + bool minus = false; bool transient_uninstalled = true; RequestType request_type = RequestType::AUTO_SELECTED; }; @@ -88,27 +95,26 @@ namespace vcpkg::Dependencies auto maybe_scf = m_provider.get_control_file(spec.name()); auto& clust = m_graph[spec]; clust.spec = spec; - if (auto p_scf = maybe_scf.get()) cluster_from_scf(*p_scf, clust); + if (auto p_scf = maybe_scf.get()) + { + clust.source = cluster_from_scf(*p_scf, clust.spec.triplet()); + } return clust; } return it->second; } private: - void cluster_from_scf(const SourceControlFile& scf, Cluster& out_cluster) const + static ClusterSource cluster_from_scf(const SourceControlFile& scf, Triplet t) { - FeatureNodeEdges core_dependencies; - core_dependencies.build_edges = - filter_dependencies_to_specs(scf.core_paragraph->depends, out_cluster.spec.triplet()); - out_cluster.edges_by_feature.emplace("core", std::move(core_dependencies)); + ClusterSource ret; + ret.build_edges.emplace("core", filter_dependencies_to_specs(scf.core_paragraph->depends, t)); for (const auto& feature : scf.feature_paragraphs) - { - FeatureNodeEdges added_edges; - added_edges.build_edges = filter_dependencies_to_specs(feature->depends, out_cluster.spec.triplet()); - out_cluster.edges_by_feature.emplace(feature->name, std::move(added_edges)); - } - out_cluster.source_control_file = &scf; + ret.build_edges.emplace(feature->name, filter_dependencies_to_specs(feature->depends, t)); + + ret.scf = &scf; + return ret; } std::unordered_map<PackageSpec, Cluster> m_graph; @@ -139,29 +145,36 @@ namespace vcpkg::Dependencies } } - InstallPlanAction::InstallPlanAction() : plan_type(InstallPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) {} + InstallPlanAction::InstallPlanAction() noexcept + : plan_type(InstallPlanType::UNKNOWN), request_type(RequestType::UNKNOWN), build_options{} + { + } InstallPlanAction::InstallPlanAction(const PackageSpec& spec, const SourceControlFile& scf, const std::set<std::string>& features, - const RequestType& request_type) + const RequestType& request_type, + std::vector<PackageSpec>&& dependencies) : spec(spec) , source_control_file(scf) , plan_type(InstallPlanType::BUILD_AND_INSTALL) , request_type(request_type) + , build_options{} , feature_list(features) + , computed_dependencies(std::move(dependencies)) { } - InstallPlanAction::InstallPlanAction(const PackageSpec& spec, - InstalledPackageView&& ipv, + InstallPlanAction::InstallPlanAction(InstalledPackageView&& ipv, const std::set<std::string>& features, const RequestType& request_type) - : spec(spec) + : 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()) { } @@ -181,7 +194,10 @@ namespace vcpkg::Dependencies return left->spec.name() < right->spec.name(); } - RemovePlanAction::RemovePlanAction() : plan_type(RemovePlanType::UNKNOWN), request_type(RequestType::UNKNOWN) {} + RemovePlanAction::RemovePlanAction() noexcept + : plan_type(RemovePlanType::UNKNOWN), request_type(RequestType::UNKNOWN) + { + } RemovePlanAction::RemovePlanAction(const PackageSpec& spec, const RemovePlanType& plan_type, @@ -210,7 +226,10 @@ namespace vcpkg::Dependencies return left->spec.name() < right->spec.name(); } - ExportPlanAction::ExportPlanAction() : plan_type(ExportPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) {} + ExportPlanAction::ExportPlanAction() noexcept + : plan_type(ExportPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) + { + } ExportPlanAction::ExportPlanAction(const PackageSpec& spec, InstalledPackageView&& installed_package, @@ -286,11 +305,11 @@ namespace vcpkg::Dependencies struct RemoveAdjacencyProvider final : Graphs::AdjacencyProvider<PackageSpec, RemovePlanAction> { const StatusParagraphs& status_db; - const std::vector<StatusParagraph*>& installed_ports; + const std::vector<InstalledPackageView>& installed_ports; const std::unordered_set<PackageSpec>& specs_as_set; RemoveAdjacencyProvider(const StatusParagraphs& status_db, - const std::vector<StatusParagraph*>& installed_ports, + const std::vector<InstalledPackageView>& installed_ports, const std::unordered_set<PackageSpec>& specs_as_set) : status_db(status_db), installed_ports(installed_ports), specs_as_set(specs_as_set) { @@ -305,24 +324,13 @@ namespace vcpkg::Dependencies const PackageSpec& spec = plan.spec; std::vector<PackageSpec> dependents; - for (const StatusParagraph* an_installed_package : installed_ports) + for (auto&& ipv : installed_ports) { - if (an_installed_package->package.spec.triplet() != spec.triplet()) continue; + auto deps = ipv.dependencies(); - std::vector<std::string> deps = an_installed_package->package.depends; - // <hack> - // This is a hack to work around existing installations that put featurespecs into binary packages - // (example: curl[core]) Eventually, this can be returned to a simple string search. - for (auto&& dep : deps) - { - dep.erase(std::find(dep.begin(), dep.end(), '['), dep.end()); - } - Util::unstable_keep_if(deps, - [&](auto&& e) { return e != an_installed_package->package.spec.name(); }); - // </hack> - if (std::find(deps.begin(), deps.end(), spec.name()) == deps.end()) continue; + if (std::find(deps.begin(), deps.end(), spec) == deps.end()) continue; - dependents.push_back(an_installed_package->package.spec); + dependents.push_back(ipv.spec()); } return dependents; @@ -344,7 +352,7 @@ namespace vcpkg::Dependencies std::string to_string(const PackageSpec& spec) const override { return spec.to_string(); } }; - const std::vector<StatusParagraph*>& installed_ports = get_installed_ports(status_db); + 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}); } @@ -402,9 +410,73 @@ namespace vcpkg::Dependencies Cluster& cluster, ClusterGraph& graph, GraphPlan& graph_plan, - const std::unordered_set<std::string>& prevent_default_features = {}); + const std::unordered_set<std::string>& prevent_default_features); - static void mark_minus(Cluster& cluster, ClusterGraph& graph, GraphPlan& graph_plan); + 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) + { + 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); + + 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); + } + + 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, + res == MarkPlusResult::SUCCESS, + "Error: Unable to satisfy dependency %s of %s", + depend, + FeatureSpec(cluster.spec, feature)); + + if (&depend_cluster == &cluster) continue; + graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); + } + + return MarkPlusResult::SUCCESS; + } + } + + // The feature was not available in the installed package nor the source paragraph. + return MarkPlusResult::FEATURE_NOT_FOUND; + } MarkPlusResult mark_plus(const std::string& feature, Cluster& cluster, @@ -412,151 +484,127 @@ namespace vcpkg::Dependencies GraphPlan& graph_plan, const std::unordered_set<std::string>& prevent_default_features) { + auto& plus = cluster.plus[feature]; + if (plus) return MarkPlusResult::SUCCESS; + plus = true; + if (feature.empty()) { - if (prevent_default_features.find(cluster.spec.name()) == prevent_default_features.end()) + // Add default features for this package. This is an exact reference, so ignore prevent_default_features. + if (auto p_source = cluster.source.get()) { - // Indicates that core was not specified in the reference - - // Add default features for this package, if this is the "core" feature and we - // are not supposed to prevent default features for this package - if (auto scf = cluster.source_control_file.value_or(nullptr)) + for (auto&& default_feature : p_source->scf->core_paragraph.get()->default_features) { - for (auto&& default_feature : scf->core_paragraph.get()->default_features) + auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) { - auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) - { - return res; - } + return res; } } - - // "core" is always an implicit default feature. In case we did not add it as - // a dependency above (e.g. no default features), add it here. - auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) - { - return res; - } - - return MarkPlusResult::SUCCESS; } else { - // Skip adding the default features, as explicitly told not to. - return MarkPlusResult::SUCCESS; + Checks::exit_with_message(VCPKG_LINE_INFO, + "Error: Unable to install default features because can't find CONTROL for %s", + cluster.spec); } - } - - auto it = cluster.edges_by_feature.find(feature); - if (it == cluster.edges_by_feature.end()) return MarkPlusResult::FEATURE_NOT_FOUND; - if (cluster.edges_by_feature[feature].plus) return MarkPlusResult::SUCCESS; - - if (cluster.original_features.find(feature) == cluster.original_features.end()) - { - cluster.transient_uninstalled = true; + // "core" is always required. + return mark_plus("core", cluster, graph, graph_plan, prevent_default_features); } - if (!cluster.transient_uninstalled) + if (feature == "*") { - return MarkPlusResult::SUCCESS; - } - cluster.edges_by_feature[feature].plus = true; - - if (!cluster.original_features.empty()) - { - mark_minus(cluster, graph, graph_plan); - } + if (auto p_source = cluster.source.get()) + { + for (auto&& fpgh : p_source->scf->feature_paragraphs) + { + auto res = mark_plus(fpgh->name, cluster, graph, graph_plan, prevent_default_features); - graph_plan.install_graph.add_vertex({&cluster}); - auto& tracked = cluster.to_install_features; - tracked.insert(feature); + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: Unable to locate feature %s in %s", + fpgh->name, + cluster.spec); + } - if (feature != "core") - { - // All features implicitly depend on core - auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); + auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); - // Should be impossible for "core" to not exist - Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); - } - else - { - // Add the default features of this package. - auto res = mark_plus("", cluster, graph, graph_plan, prevent_default_features); + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); + } + else + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Unable to handle '*' because can't find CONTROL for %s", cluster.spec); + } + return MarkPlusResult::SUCCESS; } - for (auto&& depend : cluster.edges_by_feature[feature].build_edges) + if (auto p_installed = cluster.installed.get()) { - 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, - res == MarkPlusResult::SUCCESS, - "Error: Unable to satisfy dependency %s of %s", - depend, - FeatureSpec(cluster.spec, feature)); - - if (&depend_cluster == &cluster) continue; - graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); + if (p_installed->original_features.find(feature) != p_installed->original_features.end()) + { + return MarkPlusResult::SUCCESS; + } } - return MarkPlusResult::SUCCESS; + // This feature was or will be uninstalled, therefore we need to rebuild + 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) + void mark_minus(Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features) { - if (cluster.will_remove) return; - cluster.will_remove = true; - - std::unordered_set<std::string> prevent_default_features; + if (cluster.minus) return; + cluster.minus = true; + cluster.transient_uninstalled = true; - if (cluster.request_type == RequestType::USER_REQUESTED) - { - // Do not install default features for packages which the user - // installed explicitly. New default features for dependent - // clusters should still be upgraded. - prevent_default_features.insert(cluster.spec.name()); + auto p_installed = cluster.installed.get(); + auto p_source = cluster.source.get(); - // For dependent packages this is handles through the recursion - } + 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); - graph_plan.remove_graph.add_vertex({&cluster}); - for (auto&& pair : cluster.edges_by_feature) + if (p_installed) { - auto& remove_edges_edges = pair.second.remove_edges; - for (auto&& depend : remove_edges_edges) + graph_plan.remove_graph.add_vertex({&cluster}); + for (auto&& edge : p_installed->remove_edges) { - auto& depend_cluster = graph.get(depend.spec()); - if (&depend_cluster != &cluster) graph_plan.remove_graph.add_edge({&cluster}, {&depend_cluster}); - mark_minus(depend_cluster, graph, graph_plan); + 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); } - } - cluster.transient_uninstalled = true; - for (auto&& original_feature : cluster.original_features) - { - auto res = mark_plus(original_feature, cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) + // 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) { - System::println(System::Color::warning, - "Warning: could not reinstall feature %s", - FeatureSpec{cluster.spec, original_feature}); + auto res = follow_plus_dependencies(f, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) + { + System::println(System::Color::warning, + "Warning: could not reinstall feature %s", + FeatureSpec{cluster.spec, f}); + } } - } - // Check if any default features have been added - if (auto scf = cluster.source_control_file.value_or(nullptr)) - { - auto& previous_df = cluster.installed_package.core->package.default_features; - for (auto&& default_feature : scf->core_paragraph->default_features) + // Check if any default features have been added + auto& previous_df = p_installed->ipv.core->package.default_features; + for (auto&& default_feature : p_source->scf->core_paragraph->default_features) { if (std::find(previous_df.begin(), previous_df.end(), default_feature) == previous_df.end()) { - // this is a new default feature, mark it for installation - auto res = mark_plus(default_feature, cluster, graph, graph_plan); + // 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) { System::println(System::Color::warning, @@ -585,7 +633,11 @@ namespace vcpkg::Dependencies 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); + } return pgraph.serialize(); } @@ -611,37 +663,10 @@ namespace vcpkg::Dependencies { Cluster& spec_cluster = m_graph->get(spec.spec()); spec_cluster.request_type = RequestType::USER_REQUESTED; - if (spec.feature() == "*") - { - if (auto p_scf = spec_cluster.source_control_file.value_or(nullptr)) - { - for (auto&& feature : p_scf->feature_paragraphs) - { - auto res = - mark_plus(feature->name, spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); - - Checks::check_exit( - VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); - } - - auto res = mark_plus("core", spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); - Checks::check_exit( - VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); - } - else - { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Unable to handle '*' because can't find CONTROL for %s", spec.spec()); - } - } - else - { - auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); + auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); - Checks::check_exit( - VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); - } + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); m_graph_plan->install_graph.add_vertex(ClusterPtr{&spec_cluster}); } @@ -651,7 +676,7 @@ namespace vcpkg::Dependencies Cluster& spec_cluster = m_graph->get(spec); spec_cluster.request_type = RequestType::USER_REQUESTED; - mark_minus(spec_cluster, *m_graph, *m_graph_plan); + mark_minus(spec_cluster, *m_graph, *m_graph_plan, {}); } std::vector<AnyAction> PackageGraph::serialize() const @@ -666,11 +691,8 @@ namespace vcpkg::Dependencies for (auto&& p_cluster : remove_toposort) { - auto scf = *p_cluster->source_control_file.get(); - auto spec = PackageSpec::from_name_and_triplet(scf->core_paragraph->name, p_cluster->spec.triplet()) - .value_or_exit(VCPKG_LINE_INFO); plan.emplace_back(RemovePlanAction{ - std::move(spec), + std::move(p_cluster->spec), RemovePlanType::REMOVE, p_cluster->request_type, }); @@ -681,23 +703,28 @@ namespace vcpkg::Dependencies if (p_cluster->transient_uninstalled) { // If it will be transiently uninstalled, we need to issue a full installation command - auto pscf = p_cluster->source_control_file.value_or_exit(VCPKG_LINE_INFO); - Checks::check_exit(VCPKG_LINE_INFO, pscf != nullptr); + auto pscf = p_cluster->source.value_or_exit(VCPKG_LINE_INFO).scf; + + 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, *pscf, p_cluster->to_install_features, p_cluster->request_type, + std::move(dep_specs), }); } else { // If the package isn't transitively installed, still include it if the user explicitly requested it if (p_cluster->request_type != RequestType::USER_REQUESTED) continue; + auto&& installed = p_cluster->installed.value_or_exit(VCPKG_LINE_INFO); plan.emplace_back(InstallPlanAction{ - p_cluster->spec, - InstalledPackageView{p_cluster->installed_package}, - p_cluster->original_features, + InstalledPackageView{installed.ipv}, + installed.original_features, p_cluster->request_type, }); } @@ -713,44 +740,36 @@ namespace vcpkg::Dependencies auto installed_ports = get_installed_ports(status_db); - for (auto&& status_paragraph : installed_ports) + for (auto&& ipv : installed_ports) { - Cluster& cluster = graph->get(status_paragraph->package.spec); + Cluster& cluster = graph->get(ipv.spec()); cluster.transient_uninstalled = false; - auto& status_paragraph_feature = status_paragraph->package.feature; - - // In this case, empty string indicates the "core" paragraph for a package. - if (status_paragraph_feature.empty()) - { - cluster.original_features.insert("core"); - cluster.installed_package.core = status_paragraph; - } - else - { - cluster.original_features.insert(status_paragraph_feature); - cluster.installed_package.features.emplace_back(status_paragraph); - } + 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); } // Populate the graph with "remove edges", which are the reverse of the Build-Depends edges. - for (auto&& status_paragraph : installed_ports) + for (auto&& ipv : installed_ports) { - auto& spec = status_paragraph->package.spec; - auto& status_paragraph_feature = status_paragraph->package.feature; - auto reverse_edges = FeatureSpec::from_strings_and_triplet(status_paragraph->package.depends, - status_paragraph->package.spec.triplet()); + auto deps = ipv.dependencies(); - for (auto&& dependency : reverse_edges) + for (auto&& dep : deps) { - auto& dep_cluster = graph->get(dependency.spec()); - - auto depends_name = dependency.feature(); - if (depends_name.empty()) depends_name = "core"; - - auto& target_node = dep_cluster.edges_by_feature[depends_name]; - target_node.remove_edges.emplace_back(FeatureSpec{spec, status_paragraph_feature}); + auto p_installed = graph->get(dep).installed.get(); + Checks::check_exit(VCPKG_LINE_INFO, + p_installed, + "Error: database corrupted. Package %s is installed but dependency %s is not.", + ipv.spec(), + dep); + p_installed->remove_edges.emplace(ipv.spec()); } } return graph; diff --git a/toolsrc/src/vcpkg/export.cpp b/toolsrc/src/vcpkg/export.cpp index 16c84f99d..152252018 100644 --- a/toolsrc/src/vcpkg/export.cpp +++ b/toolsrc/src/vcpkg/export.cpp @@ -13,8 +13,6 @@ #include <vcpkg/paragraphs.h> #include <vcpkg/vcpkglib.h> -#include <regex> - namespace vcpkg::Export { using Dependencies::ExportPlanAction; @@ -70,8 +68,11 @@ namespace vcpkg::Export { static constexpr std::array<ExportPlanType, 2> ORDER = {ExportPlanType::ALREADY_BUILT, ExportPlanType::NOT_BUILT}; - static constexpr Build::BuildPackageOptions build_options = {Build::UseHeadVersion::NO, - Build::AllowDownloads::YES}; + static constexpr Build::BuildPackageOptions BUILD_OPTIONS = {Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN}; for (const ExportPlanType plan_type : ORDER) { @@ -84,7 +85,7 @@ namespace vcpkg::Export std::vector<const ExportPlanAction*> cont = it->second; std::sort(cont.begin(), cont.end(), &ExportPlanAction::compare_by_name); const std::string as_string = Strings::join("\n", cont, [](const ExportPlanAction* p) { - return Dependencies::to_output_string(p->request_type, p->spec.to_string(), build_options); + return Dependencies::to_output_string(p->request_type, p->spec.to_string(), BUILD_OPTIONS); }); switch (plan_type) @@ -123,7 +124,7 @@ namespace vcpkg::Export const fs::path& output_dir) { Files::Filesystem& fs = paths.get_filesystem(); - const fs::path& nuget_exe = paths.get_nuget_exe(); + const fs::path& nuget_exe = paths.get_tool_exe(Tools::NUGET); // This file will be placed in "build\native" in the nuget package. Therefore, go up two dirs. const std::string targets_redirect_content = @@ -189,7 +190,7 @@ namespace vcpkg::Export const fs::path& output_dir, const ArchiveFormat& format) { - const fs::path& cmake_exe = paths.get_cmake_exe(); + const fs::path& cmake_exe = paths.get_tool_exe(Tools::CMAKE); const std::string exported_dir_filename = raw_exported_dir.filename().u8string(); const std::string exported_archive_filename = @@ -246,12 +247,12 @@ namespace vcpkg::Export struct ExportArguments { - bool dry_run; - bool raw; - bool nuget; - bool ifw; - bool zip; - bool seven_zip; + bool dry_run = false; + bool raw = false; + bool nuget = false; + bool ifw = false; + bool zip = false; + bool seven_zip = false; Optional<std::string> maybe_output; diff --git a/toolsrc/src/vcpkg/help.cpp b/toolsrc/src/vcpkg/help.cpp index b7d355742..743619937 100644 --- a/toolsrc/src/vcpkg/help.cpp +++ b/toolsrc/src/vcpkg/help.cpp @@ -98,14 +98,10 @@ namespace vcpkg::Help " vcpkg create <pkg> <url>\n" " [archivename] Create a new package\n" " vcpkg owns <pat> Search for files in installed packages\n" - " vcpkg cache List cached compiled packages\n" + " vcpkg env Creates a clean shell environment for development or compiling.\n" " vcpkg version Display version information\n" " vcpkg contact Display contact information to send feedback\n" "\n" - //"internal commands:\n" - //" --check-build-deps <controlfile>\n" - //" --create-binary-control <controlfile>\n" - //"\n" "Options:\n" " --triplet <t> Specify the target architecture triplet.\n" " (default: %%VCPKG_DEFAULT_TRIPLET%%, see 'vcpkg help triplet')\n" diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index 46c7c53b8..fc336d6c7 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -70,8 +70,8 @@ namespace vcpkg::Install } const std::string filename = file.filename().u8string(); - if (fs::is_regular_file(status) && (Strings::case_insensitive_ascii_equals(filename.c_str(), "CONTROL") || - Strings::case_insensitive_ascii_equals(filename.c_str(), "BUILD_INFO"))) + if (fs::is_regular_file(status) && (Strings::case_insensitive_ascii_equals(filename, "CONTROL") || + Strings::case_insensitive_ascii_equals(filename, "BUILD_INFO"))) { // Do not copy the control file continue; @@ -306,7 +306,7 @@ namespace vcpkg::Install System::println("Building package %s... done", display_name_with_features); auto bcf = std::make_unique<BinaryControlFile>( - Paragraphs::try_load_cached_control_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO)); + Paragraphs::try_load_cached_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO)); auto code = aux_install(display_name_with_features, *bcf); if (action.build_options.clean_packages == Build::CleanPackages::YES) @@ -412,13 +412,15 @@ namespace vcpkg::Install static constexpr StringLiteral OPTION_RECURSE = "--recurse"; static constexpr StringLiteral OPTION_KEEP_GOING = "--keep-going"; static constexpr StringLiteral OPTION_XUNIT = "--x-xunit"; + static constexpr StringLiteral OPTION_USE_ARIA2 = "--x-use-aria2"; - static constexpr std::array<CommandSwitch, 5> INSTALL_SWITCHES = {{ + static constexpr std::array<CommandSwitch, 6> INSTALL_SWITCHES = {{ {OPTION_DRY_RUN, "Do not actually build or install"}, {OPTION_USE_HEAD_VERSION, "Install the libraries on the command line using the latest upstream sources"}, {OPTION_NO_DOWNLOADS, "Do not download new sources"}, {OPTION_RECURSE, "Allow removal of packages as part of installation"}, {OPTION_KEEP_GOING, "Continue installing packages on failure"}, + {OPTION_USE_ARIA2, "Use aria2 to perform download tasks"}, }}; static constexpr std::array<CommandSetting, 1> INSTALL_SETTINGS = {{ {OPTION_XUNIT, "File to output results in XUnit format (Internal use)"}, @@ -547,19 +549,21 @@ namespace vcpkg::Install const bool use_head_version = Util::Sets::contains(options.switches, (OPTION_USE_HEAD_VERSION)); const bool no_downloads = Util::Sets::contains(options.switches, (OPTION_NO_DOWNLOADS)); const bool is_recursive = Util::Sets::contains(options.switches, (OPTION_RECURSE)); + const bool use_aria2 = Util::Sets::contains(options.switches, (OPTION_USE_ARIA2)); const KeepGoing keep_going = to_keep_going(Util::Sets::contains(options.switches, OPTION_KEEP_GOING)); // create the plan StatusParagraphs status_db = database_load_check(paths); + Build::DownloadTool download_tool = Build::DownloadTool::BUILT_IN; + if (use_aria2) download_tool = Build::DownloadTool::ARIA2; + const Build::BuildPackageOptions install_plan_options = { Util::Enum::to_enum<Build::UseHeadVersion>(use_head_version), Util::Enum::to_enum<Build::AllowDownloads>(!no_downloads), Build::CleanBuildtrees::NO, - Build::CleanPackages::NO}; - - // Note: action_plan will hold raw pointers to SourceControlFiles from this map - std::vector<AnyAction> action_plan; + Build::CleanPackages::NO, + download_tool}; auto all_ports = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); std::unordered_map<std::string, SourceControlFile> scf_map; @@ -567,7 +571,9 @@ namespace vcpkg::Install scf_map[port->core_paragraph->name] = std::move(*port); MapPortFileProvider provider(scf_map); - action_plan = create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db); + // Note: action_plan will hold raw pointers to SourceControlFiles from this map + std::vector<AnyAction> action_plan = + create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db); if (!GlobalState::feature_packages) { @@ -668,39 +674,42 @@ namespace vcpkg::Install return nullptr; } + std::string InstallSummary::xunit_result(const PackageSpec& spec, Chrono::ElapsedTime time, BuildResult code) + { + std::string inner_block; + const char* result_string = ""; + switch (code) + { + case BuildResult::POST_BUILD_CHECKS_FAILED: + case BuildResult::FILE_CONFLICTS: + case BuildResult::BUILD_FAILED: + result_string = "Fail"; + inner_block = Strings::format("<failure><message><![CDATA[%s]]></message></failure>", to_string(code)); + break; + case BuildResult::EXCLUDED: + case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: + result_string = "Skip"; + inner_block = Strings::format("<reason><![CDATA[%s]]></reason>", to_string(code)); + break; + case BuildResult::SUCCEEDED: result_string = "Pass"; break; + default: Checks::exit_fail(VCPKG_LINE_INFO); + } + + return Strings::format(R"(<test name="%s" method="%s" time="%lld" result="%s">%s</test>)" + "\n", + spec, + spec, + time.as<std::chrono::seconds>().count(), + result_string, + inner_block); + } + std::string InstallSummary::xunit_results() const { std::string xunit_doc; for (auto&& result : results) { - std::string inner_block; - const char* result_string = ""; - switch (result.build_result.code) - { - case BuildResult::POST_BUILD_CHECKS_FAILED: - case BuildResult::FILE_CONFLICTS: - case BuildResult::BUILD_FAILED: - result_string = "Fail"; - inner_block = Strings::format("<failure><message><![CDATA[%s]]></message></failure>", - to_string(result.build_result.code)); - break; - case BuildResult::EXCLUDED: - case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: - result_string = "Skip"; - inner_block = - Strings::format("<reason><![CDATA[%s]]></reason>", to_string(result.build_result.code)); - break; - case BuildResult::SUCCEEDED: result_string = "Pass"; break; - default: Checks::exit_fail(VCPKG_LINE_INFO); - } - - xunit_doc += Strings::format(R"(<test name="%s" method="%s" time="%lld" result="%s">%s</test>)" - "\n", - result.spec, - result.spec, - result.timing.as<std::chrono::seconds>().count(), - result_string, - inner_block); + xunit_doc += xunit_result(result.spec, result.timing, result.build_result.code); } return xunit_doc; } diff --git a/toolsrc/src/vcpkg/metrics.cpp b/toolsrc/src/vcpkg/metrics.cpp index d49cadbe2..8890c067f 100644 --- a/toolsrc/src/vcpkg/metrics.cpp +++ b/toolsrc/src/vcpkg/metrics.cpp @@ -92,7 +92,7 @@ namespace vcpkg::Metrics { encoded.append("\\\""); } - else if (ch < 0x20 || ch >= 0x80) + else if (ch < 0x20 || static_cast<unsigned char>(ch) >= 0x80) { // Note: this treats incoming Strings as Latin-1 static constexpr const char HEX[16] = { @@ -245,12 +245,6 @@ namespace vcpkg::Metrics bool get_compiled_metrics_enabled() { return DISABLE_METRICS == 0; } - static fs::path get_vcpkg_root() - { - return Files::get_real_filesystem().find_file_recursively_up( - fs::stdfs::absolute(System::get_exe_path_of_current_process()), ".vcpkg-root"); - } - std::string get_MAC_user() { #if defined(_WIN32) @@ -264,17 +258,10 @@ namespace vcpkg::Metrics while (next != last) { - auto match = *next; + const auto match = *next; if (match[0] != "00-00-00-00-00-00") { - const std::string matchstr = match[0]; - const System::PowershellParameter value("Value", matchstr); - auto hash_result = System::powershell_execute_and_capture_output( - "SHA256Hash", get_vcpkg_root() / "scripts" / "SHA256Hash.ps1", {value}); - Util::erase_remove_if(hash_result, - [](char ch) { return !(ch >= 'A' && ch <= 'F') && !(ch >= '0' && ch <= '9'); }); - hash_result = Strings::ascii_to_lowercase(hash_result); - return hash_result; + return vcpkg::Commands::Hash::get_string_hash(match[0], "SHA256"); } ++next; } diff --git a/toolsrc/src/vcpkg/paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index b66d53994..77c028937 100644 --- a/toolsrc/src/vcpkg/paragraphs.cpp +++ b/toolsrc/src/vcpkg/paragraphs.cpp @@ -228,7 +228,7 @@ namespace vcpkg::Paragraphs return error_info; } - Expected<BinaryControlFile> try_load_cached_control_package(const VcpkgPaths& paths, const PackageSpec& spec) + Expected<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec) { Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = get_paragraphs(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL"); @@ -251,7 +251,13 @@ namespace vcpkg::Paragraphs LoadResults try_load_all_ports(const Files::Filesystem& fs, const fs::path& ports_dir) { LoadResults ret; - for (auto&& path : fs.get_files_non_recursive(ports_dir)) + auto port_dirs = fs.get_files_non_recursive(ports_dir); + Util::sort(port_dirs); + Util::erase_remove_if(port_dirs, [&](auto&& port_dir_entry) { + return fs.is_regular_file(port_dir_entry) && port_dir_entry.filename() == ".DS_Store"; + }); + + for (auto&& path : port_dirs) { auto maybe_spgh = try_load_port(fs, path); if (const auto spgh = maybe_spgh.get()) diff --git a/toolsrc/src/vcpkg/parse.cpp b/toolsrc/src/vcpkg/parse.cpp index c2670f561..d50296cf8 100644 --- a/toolsrc/src/vcpkg/parse.cpp +++ b/toolsrc/src/vcpkg/parse.cpp @@ -2,7 +2,6 @@ #include <vcpkg/parse.h> -#include <vcpkg/base/checks.h> #include <vcpkg/base/util.h> namespace vcpkg::Parse diff --git a/toolsrc/src/vcpkg/postbuildlint.cpp b/toolsrc/src/vcpkg/postbuildlint.cpp index a31518ad7..6fe11951f 100644 --- a/toolsrc/src/vcpkg/postbuildlint.cpp +++ b/toolsrc/src/vcpkg/postbuildlint.cpp @@ -361,6 +361,7 @@ namespace vcpkg::PostBuildLint std::string actual_arch; }; +#if defined(_WIN32) static std::string get_actual_architecture(const MachineType& machine_type) { switch (machine_type) @@ -374,7 +375,9 @@ namespace vcpkg::PostBuildLint default: return "Machine Type Code = " + std::to_string(static_cast<uint16_t>(machine_type)); } } +#endif +#if defined(_WIN32) static void print_invalid_architecture_files(const std::string& expected_architecture, std::vector<FileAndArch> binaries_with_invalid_architecture) { @@ -391,7 +394,6 @@ namespace vcpkg::PostBuildLint static LintStatus check_dll_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) { -#if defined(_WIN32) std::vector<FileAndArch> binaries_with_invalid_architecture; for (const fs::path& file : files) @@ -414,10 +416,10 @@ namespace vcpkg::PostBuildLint print_invalid_architecture_files(expected_architecture, binaries_with_invalid_architecture); return LintStatus::ERROR_DETECTED; } -#endif return LintStatus::SUCCESS; } +#endif static LintStatus check_lib_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) @@ -802,7 +804,9 @@ namespace vcpkg::PostBuildLint check_outdated_crt_linkage_of_dlls(dlls, toolset.dumpbin, build_info, pre_build_info); } +#if defined(_WIN32) error_count += check_dll_architecture(pre_build_info.target_architecture, dlls); +#endif break; } case Build::LinkageType::STATIC: diff --git a/toolsrc/src/vcpkg/remove.cpp b/toolsrc/src/vcpkg/remove.cpp index 32433b234..13cc9325e 100644 --- a/toolsrc/src/vcpkg/remove.cpp +++ b/toolsrc/src/vcpkg/remove.cpp @@ -201,9 +201,9 @@ namespace vcpkg::Remove static std::vector<std::string> valid_arguments(const VcpkgPaths& paths) { const StatusParagraphs status_db = database_load_check(paths); - const std::vector<StatusParagraph*> installed_packages = get_installed_ports(status_db); + auto installed_packages = get_installed_ports(status_db); - return Util::fmap(installed_packages, [](auto&& pgh) -> std::string { return pgh->package.spec.to_string(); }); + return Util::fmap(installed_packages, [](auto&& pgh) -> std::string { return pgh.spec().to_string(); }); } const CommandStructure COMMAND_STRUCTURE = { diff --git a/toolsrc/src/vcpkg/sourceparagraph.cpp b/toolsrc/src/vcpkg/sourceparagraph.cpp index 0b4baf189..ed61cb42a 100644 --- a/toolsrc/src/vcpkg/sourceparagraph.cpp +++ b/toolsrc/src/vcpkg/sourceparagraph.cpp @@ -158,6 +158,16 @@ namespace vcpkg return std::move(control_file); } + Optional<const FeatureParagraph&> SourceControlFile::find_feature(const std::string& featurename) const + { + auto it = Util::find_if(feature_paragraphs, + [&](const std::unique_ptr<FeatureParagraph>& p) { return p->name == featurename; }); + if (it != feature_paragraphs.end()) + return **it; + else + return nullopt; + } + Dependency Dependency::parse_dependency(std::string name, std::string qualifier) { Dependency dep; diff --git a/toolsrc/src/vcpkg/statusparagraph.cpp b/toolsrc/src/vcpkg/statusparagraph.cpp index 62d1d4b42..462d8d8ed 100644 --- a/toolsrc/src/vcpkg/statusparagraph.cpp +++ b/toolsrc/src/vcpkg/statusparagraph.cpp @@ -12,7 +12,7 @@ namespace vcpkg static const std::string STATUS = "Status"; } - StatusParagraph::StatusParagraph() : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) {} + StatusParagraph::StatusParagraph() noexcept : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) {} void serialize(const StatusParagraph& pgh, std::string& out_str) { @@ -25,6 +25,7 @@ namespace vcpkg } StatusParagraph::StatusParagraph(std::unordered_map<std::string, std::string>&& fields) + : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) { auto status_it = fields.find(BinaryParagraphRequiredField::STATUS); Checks::check_exit(VCPKG_LINE_INFO, status_it != fields.end(), "Expected 'Status' field in status paragraph"); @@ -95,7 +96,7 @@ namespace vcpkg // Add the core paragraph dependencies to the list deps.insert(deps.end(), core->package.depends.begin(), core->package.depends.end()); - auto&& spec = core->package.spec; + auto&& l_spec = spec(); // <hack> // This is a hack to work around existing installations that put featurespecs into binary packages @@ -104,12 +105,12 @@ namespace vcpkg { dep.erase(std::find(dep.begin(), dep.end(), '['), dep.end()); } - Util::unstable_keep_if(deps, [&](auto&& e) { return e != spec.name(); }); + Util::unstable_keep_if(deps, [&](auto&& e) { return e != l_spec.name(); }); // </hack> Util::sort_unique_erase(deps); return Util::fmap(deps, [&](const std::string& dep) -> PackageSpec { - auto maybe_dependency_spec = PackageSpec::from_name_and_triplet(dep, spec.triplet()); + auto maybe_dependency_spec = PackageSpec::from_name_and_triplet(dep, l_spec.triplet()); if (auto dependency_spec = maybe_dependency_spec.get()) { return std::move(*dependency_spec); @@ -120,7 +121,7 @@ namespace vcpkg "Invalid dependency [%s] in package [%s]\n" "%s", dep, - spec.name(), + l_spec.name(), vcpkg::to_string(error_type)); }); } diff --git a/toolsrc/src/vcpkg/triplet.cpp b/toolsrc/src/vcpkg/triplet.cpp index ef0fab183..c4ad3f690 100644 --- a/toolsrc/src/vcpkg/triplet.cpp +++ b/toolsrc/src/vcpkg/triplet.cpp @@ -1,6 +1,5 @@ #include "pch.h" -#include <vcpkg/base/checks.h> #include <vcpkg/base/strings.h> #include <vcpkg/triplet.h> @@ -38,7 +37,7 @@ namespace vcpkg const Triplet Triplet::ARM_UWP = from_canonical_name("arm-uwp"); const Triplet Triplet::ARM64_UWP = from_canonical_name("arm64-uwp"); const Triplet Triplet::ARM_WINDOWS = from_canonical_name("arm-windows"); - const Triplet Triplet::ARM64_WINDOWS = from_canonical_name("arm64-windows"); + const Triplet Triplet::ARM64_WINDOWS = from_canonical_name("arm64-windows"); bool Triplet::operator==(const Triplet& other) const { return this->m_instance == other.m_instance; } diff --git a/toolsrc/src/vcpkg/update.cpp b/toolsrc/src/vcpkg/update.cpp index d6c5614ed..57259f952 100644 --- a/toolsrc/src/vcpkg/update.cpp +++ b/toolsrc/src/vcpkg/update.cpp @@ -17,17 +17,12 @@ namespace vcpkg::Update std::vector<OutdatedPackage> find_outdated_packages(const Dependencies::PortFileProvider& provider, const StatusParagraphs& status_db) { - const std::vector<StatusParagraph*> installed_packages = get_installed_ports(status_db); + auto installed_packages = get_installed_ports(status_db); std::vector<OutdatedPackage> output; - for (const StatusParagraph* pgh : installed_packages) + for (auto&& ipv : installed_packages) { - if (!pgh->package.feature.empty()) - { - // Skip feature paragraphs; only consider master paragraphs for needing updates. - continue; - } - + const auto& pgh = ipv.core; auto maybe_scf = provider.get_control_file(pgh->package.spec.name()); if (auto p_scf = maybe_scf.get()) { diff --git a/toolsrc/src/vcpkg/userconfig.cpp b/toolsrc/src/vcpkg/userconfig.cpp index 9db2e5d47..4945fdaaa 100644 --- a/toolsrc/src/vcpkg/userconfig.cpp +++ b/toolsrc/src/vcpkg/userconfig.cpp @@ -29,16 +29,21 @@ namespace namespace vcpkg { - static fs::path get_config_path() + fs::path get_user_dir() { #if defined(_WIN32) - return get_localappdata() / "vcpkg" / "config"; + return get_localappdata() / "vcpkg"; #else auto maybe_home = System::get_environment_variable("HOME"); - return fs::path(maybe_home.value_or("/var")) / "vcpkg" / "config"; + return fs::path(maybe_home.value_or("/var")) / ".vcpkg"; #endif } + static fs::path get_config_path() + { + return get_user_dir() / "config"; + } + UserConfig UserConfig::try_read_data(const Files::Filesystem& fs) { UserConfig ret; diff --git a/toolsrc/src/vcpkg/vcpkglib.cpp b/toolsrc/src/vcpkg/vcpkglib.cpp index 220c29720..c8e95dab1 100644 --- a/toolsrc/src/vcpkg/vcpkglib.cpp +++ b/toolsrc/src/vcpkg/vcpkglib.cpp @@ -54,6 +54,7 @@ namespace vcpkg StatusParagraphs current_status_db = load_current_database(fs, status_file, status_file_old); auto update_files = fs.get_files_non_recursive(updates_dir); + Util::sort(update_files); if (update_files.empty()) { // updates directory is empty, control file is up-to-date. @@ -168,16 +169,32 @@ namespace vcpkg fs.rename(updated_listfile_path, listfile_path); } - std::vector<StatusParagraph*> get_installed_ports(const StatusParagraphs& status_db) + std::vector<InstalledPackageView> get_installed_ports(const StatusParagraphs& status_db) { - std::vector<StatusParagraph*> installed_packages; + std::map<PackageSpec, InstalledPackageView> ipv_map; + + std::vector<InstalledPackageView> installed_packages; for (auto&& pgh : status_db) { if (!pgh->is_installed()) continue; - installed_packages.push_back(pgh.get()); + auto& ipv = ipv_map[pgh->package.spec]; + if (pgh->package.feature.empty()) + { + ipv.core = pgh.get(); + } + else + { + ipv.features.emplace_back(pgh.get()); + } } - return installed_packages; + for (auto&& ipv : ipv_map) + Checks::check_exit(VCPKG_LINE_INFO, + ipv.second.core != nullptr, + "Database is corrupted: package %s has features but no core paragraph.", + ipv.first); + + return Util::fmap(ipv_map, [](auto&& p) -> InstalledPackageView { return std::move(p.second); }); } std::vector<StatusParagraphAndAssociatedFiles> get_installed_files(const VcpkgPaths& paths, diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 46e80c4a9..0903c2d76 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -5,294 +5,13 @@ #include <vcpkg/base/system.h> #include <vcpkg/base/util.h> #include <vcpkg/build.h> +#include <vcpkg/commands.h> #include <vcpkg/metrics.h> #include <vcpkg/packagespec.h> #include <vcpkg/vcpkgpaths.h> namespace vcpkg { - static constexpr CStringView V_120 = "v120"; - static constexpr CStringView V_140 = "v140"; - static constexpr CStringView V_141 = "v141"; - - struct ToolData - { - std::array<int, 3> required_version; - fs::path downloaded_exe_path; - }; - - static Optional<std::array<int, 3>> parse_version_string(const std::string& version_as_string) - { - static const std::regex RE(R"###((\d+)\.(\d+)\.(\d+))###"); - - std::match_results<std::string::const_iterator> match; - const auto found = std::regex_search(version_as_string, match, RE); - if (!found) - { - return {}; - } - - const int d1 = atoi(match[1].str().c_str()); - const int d2 = atoi(match[2].str().c_str()); - const int d3 = atoi(match[3].str().c_str()); - const std::array<int, 3> result = {d1, d2, d3}; - return result; - } - - static ToolData parse_tool_data_from_xml(const VcpkgPaths& paths, const std::string& tool) - { - static const fs::path XML_PATH = paths.scripts / "vcpkgTools.xml"; - - const auto get_string_inside_tags = - [](const std::string& input, const std::regex& regex, const std::string& tag_name) -> std::string { - std::smatch match; - const bool has_match = std::regex_search(input.cbegin(), input.cend(), match, regex); - Checks::check_exit( - VCPKG_LINE_INFO, has_match, "Could not find tag <%s> in %s", tag_name, XML_PATH.generic_string()); - - return match[1]; - }; - - static const std::string XML = paths.get_filesystem().read_contents(XML_PATH).value_or_exit(VCPKG_LINE_INFO); - static const std::regex VERSION_REGEX{ - Strings::format(R"###(<requiredVersion>([\s\S]*?)</requiredVersion>)###", tool)}; - static const std::regex EXE_RELATIVE_PATH_REGEX{ - Strings::format(R"###(<exeRelativePath>([\s\S]*?)</exeRelativePath>)###", tool)}; - - const std::regex tool_regex{Strings::format(R"###(<tool[\s]+name="%s">([\s\S]*?)</tool>)###", tool)}; - - std::smatch match_tool; - const bool has_match_tool = std::regex_search(XML.cbegin(), XML.cend(), match_tool, tool_regex); - Checks::check_exit(VCPKG_LINE_INFO, - has_match_tool, - "Could not find entry for tool [%s] in %s", - tool, - XML_PATH.generic_string()); - - const std::string tool_data_as_string = get_string_inside_tags(XML, tool_regex, tool); - - const std::string required_version_as_string = - get_string_inside_tags(tool_data_as_string, VERSION_REGEX, "requiredVersion"); - - const std::string exe_relative_path = - get_string_inside_tags(tool_data_as_string, EXE_RELATIVE_PATH_REGEX, "exeRelativePath"); - - const Optional<std::array<int, 3>> required_version = parse_version_string(required_version_as_string); - Checks::check_exit(VCPKG_LINE_INFO, - required_version.has_value(), - "Could not parse version for tool %s. Version string was: %s", - tool, - required_version_as_string); - - const fs::path exe_path = paths.downloads / exe_relative_path; - return ToolData{*required_version.get(), exe_path}; - } - - static bool exists_and_has_equal_or_greater_version(const std::string& version_cmd, - const std::array<int, 3>& expected_version) - { - const auto rc = System::cmd_execute_and_capture_output(Strings::format(R"(%s)", version_cmd)); - if (rc.exit_code != 0) - { - return false; - } - - const Optional<std::array<int, 3>> v = parse_version_string(rc.output); - if (!v.has_value()) - { - return false; - } - - const std::array<int, 3> actual_version = *v.get(); - return (actual_version[0] > expected_version[0] || - (actual_version[0] == expected_version[0] && actual_version[1] > expected_version[1]) || - (actual_version[0] == expected_version[0] && actual_version[1] == expected_version[1] && - actual_version[2] >= expected_version[2])); - } - - static Optional<fs::path> find_if_has_equal_or_greater_version(const std::vector<fs::path>& candidate_paths, - const std::string& version_check_arguments, - const std::array<int, 3>& expected_version) - { - auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { - const std::string cmd = Strings::format(R"("%s" %s)", p.u8string(), version_check_arguments); - return exists_and_has_equal_or_greater_version(cmd, expected_version); - }); - - if (it != candidate_paths.cend()) - { - return std::move(*it); - } - - return nullopt; - } - - static std::vector<std::string> keep_data_lines(const std::string& data_blob) - { - static const std::regex DATA_LINE_REGEX(R"(<sol>::(.+?)(?=::<eol>))"); - - std::vector<std::string> data_lines; - - const std::sregex_iterator it(data_blob.cbegin(), data_blob.cend(), DATA_LINE_REGEX); - const std::sregex_iterator end; - for (std::sregex_iterator i = it; i != end; ++i) - { - const std::smatch match = *i; - data_lines.push_back(match[1].str()); - } - - return data_lines; - } - - static fs::path fetch_tool(const fs::path& scripts_folder, const std::string& tool_name, const ToolData& tool_data) - { - const std::array<int, 3>& version = tool_data.required_version; - - const std::string version_as_string = Strings::format("%d.%d.%d", version[0], version[1], version[2]); - System::println("A suitable version of %s was not found (required v%s). Downloading portable %s v%s...", - tool_name, - version_as_string, - tool_name, - version_as_string); - const fs::path script = scripts_folder / "fetchtool.ps1"; - const std::string title = Strings::format( - "Fetching %s version %s (No sufficient installed version was found)", tool_name, version_as_string); - const System::PowershellParameter tool_param("tool", tool_name); - const std::string output = System::powershell_execute_and_capture_output(title, script, {tool_param}); - - const std::vector<std::string> tool_path = keep_data_lines(output); - Checks::check_exit(VCPKG_LINE_INFO, tool_path.size() == 1, "Expected tool path, but got %s", output); - - const fs::path actual_downloaded_path = Strings::trim(std::string{tool_path.at(0)}); - const fs::path& expected_downloaded_path = tool_data.downloaded_exe_path; - std::error_code ec; - const auto eq = fs::stdfs::equivalent(expected_downloaded_path, actual_downloaded_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - eq && !ec, - "Expected tool downloaded path to be %s, but was %s", - expected_downloaded_path.u8string(), - actual_downloaded_path.u8string()); - return actual_downloaded_path; - } - - static fs::path get_cmake_path(const VcpkgPaths& paths) - { -#if defined(_WIN32) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "cmake"); -#else - static const ToolData TOOL_DATA = ToolData{{3, 5, 1}, ""}; -#endif - static const std::string VERSION_CHECK_ARGUMENTS = "--version"; - - std::vector<fs::path> candidate_paths; -#if defined(_WIN32) - candidate_paths.push_back(TOOL_DATA.downloaded_exe_path); -#endif - const std::vector<fs::path> from_path = Files::find_from_PATH("cmake"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); -#if defined(_WIN32) - candidate_paths.push_back(System::get_program_files_platform_bitness() / "CMake" / "bin" / "cmake.exe"); - candidate_paths.push_back(System::get_program_files_32_bit() / "CMake" / "bin"); -#endif - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.required_version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths.scripts, "cmake", TOOL_DATA); - } - - static fs::path get_7za_path(const VcpkgPaths& paths) - { -#if defined(_WIN32) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "7zip"); - if (!paths.get_filesystem().exists(TOOL_DATA.downloaded_exe_path)) - { - return fetch_tool(paths.scripts, "7zip", TOOL_DATA); - } - return TOOL_DATA.downloaded_exe_path; -#else - Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot download 7zip for non-Windows platforms."); -#endif - } - - static fs::path get_nuget_path(const VcpkgPaths& paths) - { - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "nuget"); - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(TOOL_DATA.downloaded_exe_path); - const std::vector<fs::path> from_path = Files::find_from_PATH("nuget"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - - auto path = find_if_has_equal_or_greater_version(candidate_paths, "", TOOL_DATA.required_version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths.scripts, "nuget", TOOL_DATA); - } - - static fs::path get_git_path(const VcpkgPaths& paths) - { -#if defined(_WIN32) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "git"); -#else - static const ToolData TOOL_DATA = ToolData{{2, 7, 4}, ""}; -#endif - static const std::string VERSION_CHECK_ARGUMENTS = "--version"; - - std::vector<fs::path> candidate_paths; -#if defined(_WIN32) - candidate_paths.push_back(TOOL_DATA.downloaded_exe_path); -#endif - const std::vector<fs::path> from_path = Files::find_from_PATH("git"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); -#if defined(_WIN32) - candidate_paths.push_back(System::get_program_files_platform_bitness() / "git" / "cmd" / "git.exe"); - candidate_paths.push_back(System::get_program_files_32_bit() / "git" / "cmd" / "git.exe"); -#endif - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.required_version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths.scripts, "git", TOOL_DATA); - } - - static fs::path get_ifw_installerbase_path(const VcpkgPaths& paths) - { - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "installerbase"); - - static const std::string VERSION_CHECK_ARGUMENTS = "--framework-version"; - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(TOOL_DATA.downloaded_exe_path); - // TODO: Uncomment later - // const std::vector<fs::path> from_path = Files::find_from_PATH("installerbase"); - // candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / - // "Tools" / "QtInstallerFramework" / "3.1" / "bin" / "installerbase.exe"); - // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / - // "QtIFW-3.1.0" / "bin" / "installerbase.exe"); - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.required_version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths.scripts, "installerbase", TOOL_DATA); - } - Expected<VcpkgPaths> VcpkgPaths::create(const fs::path& vcpkg_root_dir, const std::string& default_vs_path) { std::error_code ec; @@ -356,6 +75,7 @@ namespace vcpkg { output.push_back(path.stem().filename().string()); } + Util::sort(output); return output; }); @@ -363,261 +83,15 @@ namespace vcpkg bool VcpkgPaths::is_valid_triplet(const Triplet& t) const { - auto it = Util::find_if(this->get_available_triplets(), - [&](auto&& available_triplet) { return t.canonical_name() == available_triplet; }); + const auto it = Util::find_if(this->get_available_triplets(), [&](auto&& available_triplet) { + return t.canonical_name() == available_triplet; + }); return it != this->get_available_triplets().cend(); } - const fs::path& VcpkgPaths::get_7za_exe() const - { - return this->_7za_exe.get_lazy([this]() { return get_7za_path(*this); }); - } - - const fs::path& VcpkgPaths::get_cmake_exe() const - { - return this->cmake_exe.get_lazy([this]() { return get_cmake_path(*this); }); - } - - const fs::path& VcpkgPaths::get_git_exe() const - { - return this->git_exe.get_lazy([this]() { return get_git_path(*this); }); - } - - const fs::path& VcpkgPaths::get_nuget_exe() const - { - return this->nuget_exe.get_lazy([this]() { return get_nuget_path(*this); }); - } - - const fs::path& VcpkgPaths::get_ifw_installerbase_exe() const - { - return this->ifw_installerbase_exe.get_lazy([this]() { return get_ifw_installerbase_path(*this); }); - } - - const fs::path& VcpkgPaths::get_ifw_binarycreator_exe() const - { - return this->ifw_binarycreator_exe.get_lazy( - [this]() { return get_ifw_installerbase_exe().parent_path() / "binarycreator.exe"; }); - } - - const fs::path& VcpkgPaths::get_ifw_repogen_exe() const - { - return this->ifw_repogen_exe.get_lazy( - [this]() { return get_ifw_installerbase_exe().parent_path() / "repogen.exe"; }); - } - - struct VisualStudioInstance - { - fs::path root_path; - std::string version; - std::string release_type; - std::string preference_weight; // Mostly unused, just for verification that order is as intended - - std::string major_version() const { return version.substr(0, 2); } - }; - - static std::vector<VisualStudioInstance> get_visual_studio_instances(const VcpkgPaths& paths) - { - const fs::path script = paths.scripts / "findVisualStudioInstallationInstances.ps1"; - const std::string output = - System::powershell_execute_and_capture_output("Detecting Visual Studio instances", script); - - const std::vector<std::string> instances_as_strings = keep_data_lines(output); - Checks::check_exit(VCPKG_LINE_INFO, - !instances_as_strings.empty(), - "Could not detect any Visual Studio instances.\n" - "Powershell script:\n" - " %s\n" - "returned:\n" - "%s", - script.generic_string(), - output); - - std::vector<VisualStudioInstance> instances; - for (const std::string& instance_as_string : instances_as_strings) - { - const std::vector<std::string> split = Strings::split(instance_as_string, "::"); - Checks::check_exit(VCPKG_LINE_INFO, - split.size() == 4, - "Invalid Visual Studio instance format.\n" - "Expected: PreferenceWeight::ReleaseType::Version::PathToVisualStudio\n" - "Actual : %s\n", - instance_as_string); - instances.push_back({split.at(3), split.at(2), split.at(1), split.at(0)}); - } - - return instances; - } - - static std::vector<Toolset> find_toolset_instances(const VcpkgPaths& paths) + const fs::path& VcpkgPaths::get_tool_exe(const std::string& tool) const { - using CPU = System::CPUArchitecture; - - const auto& fs = paths.get_filesystem(); - - // Note: this will contain a mix of vcvarsall.bat locations and dumpbin.exe locations. - std::vector<fs::path> paths_examined; - - std::vector<Toolset> found_toolsets; - std::vector<Toolset> excluded_toolsets; - - const std::vector<VisualStudioInstance> vs_instances = get_visual_studio_instances(paths); - const bool v140_is_available = Util::find_if(vs_instances, [&](const VisualStudioInstance& vs_instance) { - return vs_instance.major_version() == "14"; - }) != vs_instances.cend(); - - for (const VisualStudioInstance& vs_instance : vs_instances) - { - const std::string major_version = vs_instance.major_version(); - if (major_version == "15") - { - const fs::path vc_dir = vs_instance.root_path / "VC"; - - // Skip any instances that do not have vcvarsall. - const fs::path vcvarsall_dir = vc_dir / "Auxiliary" / "Build"; - const fs::path vcvarsall_bat = vcvarsall_dir / "vcvarsall.bat"; - paths_examined.push_back(vcvarsall_bat); - if (!fs.exists(vcvarsall_bat)) continue; - - // Get all supported architectures - std::vector<ToolsetArchOption> supported_architectures; - if (fs.exists(vcvarsall_dir / "vcvars32.bat")) - supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); - if (fs.exists(vcvarsall_dir / "vcvars64.bat")) - supported_architectures.push_back({"amd64", CPU::X64, CPU::X64}); - if (fs.exists(vcvarsall_dir / "vcvarsx86_amd64.bat")) - supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); - if (fs.exists(vcvarsall_dir / "vcvarsx86_arm.bat")) - supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); - if (fs.exists(vcvarsall_dir / "vcvarsx86_arm64.bat")) - supported_architectures.push_back({"x86_arm64", CPU::X86, CPU::ARM64}); - if (fs.exists(vcvarsall_dir / "vcvarsamd64_x86.bat")) - supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); - if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm.bat")) - supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); - if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm64.bat")) - supported_architectures.push_back({"amd64_arm64", CPU::X64, CPU::ARM64}); - - // Locate the "best" MSVC toolchain version - const fs::path msvc_path = vc_dir / "Tools" / "MSVC"; - std::vector<fs::path> msvc_subdirectories = fs.get_files_non_recursive(msvc_path); - Util::unstable_keep_if(msvc_subdirectories, - [&fs](const fs::path& path) { return fs.is_directory(path); }); - - // Sort them so that latest comes first - std::sort( - msvc_subdirectories.begin(), - msvc_subdirectories.end(), - [](const fs::path& left, const fs::path& right) { return left.filename() > right.filename(); }); - - for (const fs::path& subdir : msvc_subdirectories) - { - const fs::path dumpbin_path = subdir / "bin" / "HostX86" / "x86" / "dumpbin.exe"; - paths_examined.push_back(dumpbin_path); - if (fs.exists(dumpbin_path)) - { - const Toolset v141toolset = Toolset{ - vs_instance.root_path, dumpbin_path, vcvarsall_bat, {}, V_141, supported_architectures}; - - auto english_language_pack = dumpbin_path.parent_path() / "1033"; - - if (!fs.exists(english_language_pack)) - { - excluded_toolsets.push_back(v141toolset); - break; - } - - found_toolsets.push_back(v141toolset); - - if (v140_is_available) - { - const Toolset v140toolset = Toolset{vs_instance.root_path, - dumpbin_path, - vcvarsall_bat, - {"-vcvars_ver=14.0"}, - V_140, - supported_architectures}; - found_toolsets.push_back(v140toolset); - } - - break; - } - } - - continue; - } - - if (major_version == "14" || major_version == "12") - { - const fs::path vcvarsall_bat = vs_instance.root_path / "VC" / "vcvarsall.bat"; - - paths_examined.push_back(vcvarsall_bat); - if (fs.exists(vcvarsall_bat)) - { - const fs::path vs_dumpbin_exe = vs_instance.root_path / "VC" / "bin" / "dumpbin.exe"; - paths_examined.push_back(vs_dumpbin_exe); - - const fs::path vs_bin_dir = vcvarsall_bat.parent_path() / "bin"; - std::vector<ToolsetArchOption> supported_architectures; - if (fs.exists(vs_bin_dir / "vcvars32.bat")) - supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); - if (fs.exists(vs_bin_dir / "amd64\\vcvars64.bat")) - supported_architectures.push_back({"x64", CPU::X64, CPU::X64}); - if (fs.exists(vs_bin_dir / "x86_amd64\\vcvarsx86_amd64.bat")) - supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); - if (fs.exists(vs_bin_dir / "x86_arm\\vcvarsx86_arm.bat")) - supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); - if (fs.exists(vs_bin_dir / "amd64_x86\\vcvarsamd64_x86.bat")) - supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); - if (fs.exists(vs_bin_dir / "amd64_arm\\vcvarsamd64_arm.bat")) - supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); - - if (fs.exists(vs_dumpbin_exe)) - { - const Toolset toolset = {vs_instance.root_path, - vs_dumpbin_exe, - vcvarsall_bat, - {}, - major_version == "14" ? V_140 : V_120, - supported_architectures}; - - auto english_language_pack = vs_dumpbin_exe.parent_path() / "1033"; - - if (!fs.exists(english_language_pack)) - { - excluded_toolsets.push_back(toolset); - break; - } - - found_toolsets.push_back(toolset); - } - } - } - } - - if (!excluded_toolsets.empty()) - { - System::println( - System::Color::warning, - "Warning: The following VS instances are excluded because the English language pack is unavailable."); - for (const Toolset& toolset : excluded_toolsets) - { - System::println(" %s", toolset.visual_studio_root_path.u8string()); - } - System::println(System::Color::warning, "Please install the English language pack."); - } - - if (found_toolsets.empty()) - { - System::println(System::Color::error, "Could not locate a complete toolset."); - System::println("The following paths were examined:"); - for (const fs::path& path : paths_examined) - { - System::println(" %s", path.u8string()); - } - Checks::exit_fail(VCPKG_LINE_INFO); - } - - return found_toolsets; + return this->tool_paths.get_lazy(tool, [&]() { return Commands::Fetch::get_tool_path(*this, tool); }); } const Toolset& VcpkgPaths::get_toolset(const Build::PreBuildInfo& prebuildinfo) const @@ -641,7 +115,7 @@ namespace vcpkg // Invariant: toolsets are non-empty and sorted with newest at back() const std::vector<Toolset>& vs_toolsets = - this->toolsets.get_lazy([this]() { return find_toolset_instances(*this); }); + this->toolsets.get_lazy([this]() { return Commands::Fetch::find_toolset_instances(*this); }); std::vector<const Toolset*> candidates = Util::element_pointers(vs_toolsets); const auto tsv = prebuildinfo.platform_toolset.get(); diff --git a/toolsrc/src/vcpkg/versiont.cpp b/toolsrc/src/vcpkg/versiont.cpp index 0d4a39255..d20e6b577 100644 --- a/toolsrc/src/vcpkg/versiont.cpp +++ b/toolsrc/src/vcpkg/versiont.cpp @@ -5,7 +5,7 @@ namespace vcpkg { - VersionT::VersionT() : value("0.0.0") {} + VersionT::VersionT() noexcept : value("0.0.0") {} VersionT::VersionT(std::string&& value) : value(std::move(value)) {} VersionT::VersionT(const std::string& value) : value(value) {} const std::string& VersionT::to_string() const { return value; } @@ -13,7 +13,7 @@ namespace vcpkg bool operator!=(const VersionT& left, const VersionT& right) { return left.to_string() != right.to_string(); } std::string to_printf_arg(const VersionT& version) { return version.to_string(); } - VersionDiff::VersionDiff() : left(), right() {} + VersionDiff::VersionDiff() noexcept : left(), right() {} VersionDiff::VersionDiff(const VersionT& left, const VersionT& right) : left(left), right(right) {} std::string VersionDiff::to_string() const |
