diff options
Diffstat (limited to 'toolsrc/src')
72 files changed, 6271 insertions, 3446 deletions
diff --git a/toolsrc/src/tests.arguments.cpp b/toolsrc/src/tests.arguments.cpp deleted file mode 100644 index 51ababd3d..000000000 --- a/toolsrc/src/tests.arguments.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "tests.pch.h"
-
-#if defined(_WIN32)
-#pragma comment(lib, "version")
-#pragma comment(lib, "winhttp")
-#endif
-
-using namespace Microsoft::VisualStudio::CppUnitTestFramework;
-
-using namespace vcpkg;
-
-namespace UnitTest1
-{
- class ArgumentTests : public TestClass<ArgumentTests>
- {
- TEST_METHOD(create_from_arg_sequence_options_lower)
- {
- std::vector<std::string> t = {"--vcpkg-root", "C:\\vcpkg", "--debug", "--sendmetrics", "--printmetrics"};
- auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
- Assert::AreEqual("C:\\vcpkg", v.vcpkg_root_dir.get()->c_str());
- Assert::IsTrue(v.debug && *v.debug.get());
- Assert::IsTrue(v.sendmetrics && v.sendmetrics.get());
- Assert::IsTrue(v.printmetrics && *v.printmetrics.get());
- }
-
- TEST_METHOD(create_from_arg_sequence_options_upper)
- {
- std::vector<std::string> t = {"--VCPKG-ROOT", "C:\\vcpkg", "--DEBUG", "--SENDMETRICS", "--PRINTMETRICS"};
- auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
- Assert::AreEqual("C:\\vcpkg", v.vcpkg_root_dir.get()->c_str());
- Assert::IsTrue(v.debug && *v.debug.get());
- Assert::IsTrue(v.sendmetrics && v.sendmetrics.get());
- Assert::IsTrue(v.printmetrics && *v.printmetrics.get());
- }
-
- TEST_METHOD(create_from_arg_sequence_valued_options)
- {
- std::array<CommandSetting, 1> settings = {{{"--a", ""}}};
- CommandStructure cmdstruct = {"", 0, SIZE_MAX, {{}, settings}, nullptr};
-
- std::vector<std::string> t = {"--a=b", "command", "argument"};
- auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
- auto opts = v.parse_arguments(cmdstruct);
- Assert::AreEqual("b", opts.settings["--a"].c_str());
- Assert::AreEqual(size_t{1}, v.command_arguments.size());
- Assert::AreEqual("argument", v.command_arguments[0].c_str());
- Assert::AreEqual("command", v.command.c_str());
- }
-
- TEST_METHOD(create_from_arg_sequence_valued_options2)
- {
- std::array<CommandSwitch, 2> switches = {{{"--a", ""}, {"--c", ""}}};
- std::array<CommandSetting, 2> settings = {{{"--b", ""}, {"--d", ""}}};
- CommandStructure cmdstruct = {"", 0, SIZE_MAX, {switches, settings}, nullptr};
-
- std::vector<std::string> t = {"--a", "--b=c"};
- auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
- auto opts = v.parse_arguments(cmdstruct);
- Assert::AreEqual("c", opts.settings["--b"].c_str());
- Assert::IsTrue(opts.settings.find("--d") == opts.settings.end());
- Assert::IsTrue(opts.switches.find("--a") != opts.switches.end());
- Assert::IsTrue(opts.settings.find("--c") == opts.settings.end());
- Assert::AreEqual(size_t{0}, v.command_arguments.size());
- }
- };
-}
diff --git a/toolsrc/src/tests.chrono.cpp b/toolsrc/src/tests.chrono.cpp deleted file mode 100644 index 269cdca58..000000000 --- a/toolsrc/src/tests.chrono.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "tests.pch.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -namespace Chrono = vcpkg::Chrono; - -namespace UnitTest1 -{ - class ChronoTests : public TestClass<ChronoTests> - { - TEST_METHOD(parse_time) - { - auto timestring = "1990-02-03T04:05:06.0Z"; - auto maybe_time = Chrono::CTime::parse(timestring); - - Assert::IsTrue(maybe_time.has_value()); - - Assert::AreEqual(timestring, maybe_time.get()->to_string().c_str()); - } - - TEST_METHOD(parse_time_blank) - { - auto maybe_time = Chrono::CTime::parse(""); - - Assert::IsFalse(maybe_time.has_value()); - } - - TEST_METHOD(time_difference) - { - auto maybe_time1 = Chrono::CTime::parse("1990-02-03T04:05:06.0Z"); - auto maybe_time2 = Chrono::CTime::parse("1990-02-10T04:05:06.0Z"); - - Assert::IsTrue(maybe_time1.has_value()); - Assert::IsTrue(maybe_time2.has_value()); - - auto delta = maybe_time2.get()->to_time_point() - maybe_time1.get()->to_time_point(); - - Assert::AreEqual(24 * 7, std::chrono::duration_cast<std::chrono::hours>(delta).count()); - } - }; -} diff --git a/toolsrc/src/tests.dependencies.cpp b/toolsrc/src/tests.dependencies.cpp deleted file mode 100644 index 7d8283ed6..000000000 --- a/toolsrc/src/tests.dependencies.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "tests.pch.h" - -#if defined(_WIN32) -#pragma comment(lib, "version") -#pragma comment(lib, "winhttp") -#endif - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using namespace vcpkg; -using Parse::parse_comma_list; - -namespace UnitTest1 -{ - class DependencyTests : public TestClass<DependencyTests> - { - TEST_METHOD(parse_depends_one) - { - auto v = expand_qualified_dependencies(parse_comma_list("libA (windows)")); - Assert::AreEqual(size_t(1), v.size()); - Assert::AreEqual("libA", v[0].depend.name.c_str()); - Assert::AreEqual("windows", v[0].qualifier.c_str()); - } - - TEST_METHOD(filter_depends) - { - auto deps = expand_qualified_dependencies(parse_comma_list("libA (windows), libB, libC (uwp)")); - auto v = filter_dependencies(deps, Triplet::X64_WINDOWS); - Assert::AreEqual(size_t(2), v.size()); - Assert::AreEqual("libA", v[0].c_str()); - Assert::AreEqual("libB", v[1].c_str()); - - auto v2 = filter_dependencies(deps, Triplet::ARM_UWP); - Assert::AreEqual(size_t(2), v.size()); - Assert::AreEqual("libB", v2[0].c_str()); - Assert::AreEqual("libC", v2[1].c_str()); - } - }; - - class SupportsTests : public TestClass<SupportsTests> - { - TEST_METHOD(parse_supports_all) - { - auto v = Supports::parse({ - "x64", - "x86", - "arm", - "windows", - "uwp", - "v140", - "v141", - "crt-static", - "crt-dynamic", - }); - Assert::AreNotEqual(uintptr_t(0), uintptr_t(v.get())); - - Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::UWP, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::ARM, - Supports::Platform::WINDOWS, - Supports::Linkage::STATIC, - Supports::ToolsetVersion::V141)); - } - - TEST_METHOD(parse_supports_invalid) - { - auto v = Supports::parse({"arm64"}); - Assert::AreEqual(uintptr_t(0), uintptr_t(v.get())); - Assert::AreEqual(size_t(1), v.error().size()); - Assert::AreEqual("arm64", v.error()[0].c_str()); - } - - TEST_METHOD(parse_supports_case_sensitive) - { - auto v = Supports::parse({"Windows"}); - Assert::AreEqual(uintptr_t(0), uintptr_t(v.get())); - Assert::AreEqual(size_t(1), v.error().size()); - Assert::AreEqual("Windows", v.error()[0].c_str()); - } - - TEST_METHOD(parse_supports_some) - { - auto v = Supports::parse({ - "x64", - "x86", - "windows", - }); - Assert::AreNotEqual(uintptr_t(0), uintptr_t(v.get())); - - Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::WINDOWS, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - Assert::IsFalse(v.get()->is_supported(System::CPUArchitecture::ARM, - Supports::Platform::WINDOWS, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - Assert::IsFalse(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::UWP, - Supports::Linkage::DYNAMIC, - Supports::ToolsetVersion::V140)); - Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::X64, - Supports::Platform::WINDOWS, - Supports::Linkage::STATIC, - Supports::ToolsetVersion::V141)); - } - }; -} diff --git a/toolsrc/src/tests.packagespec.cpp b/toolsrc/src/tests.packagespec.cpp deleted file mode 100644 index d3bc18c79..000000000 --- a/toolsrc/src/tests.packagespec.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include "tests.pch.h" - -#include <tests.utils.h> - -#if defined(_WIN32) -#pragma comment(lib, "version") -#pragma comment(lib, "winhttp") -#endif - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -namespace UnitTest1 -{ - using namespace vcpkg; - - class SpecifierConversion : public TestClass<SpecifierConversion> - { - TEST_METHOD(full_package_spec_to_feature_specs) - { - auto a_spec = PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); - auto b_spec = PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); - - auto fspecs = FullPackageSpec::to_feature_specs({{a_spec, {"0", "1"}}, {b_spec, {"2", "3"}}}); - - Assert::AreEqual(size_t(6), fspecs.size()); - - std::array<const char*, 6> features = {"", "0", "1", "", "2", "3"}; - std::array<PackageSpec*, 6> specs = {&a_spec, &a_spec, &a_spec, &b_spec, &b_spec, &b_spec}; - - for (size_t i = 0; i < features.size(); ++i) - { - Assert::AreEqual(features[i], fspecs[i].feature().c_str()); - Assert::AreEqual(*specs[i], fspecs[i].spec()); - } - } - }; - - class SpecifierParsing : public TestClass<SpecifierParsing> - { - TEST_METHOD(parsed_specifier_from_string) - { - auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib"); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); - auto spec = maybe_spec.get(); - Assert::AreEqual("zlib", spec->name.c_str()); - Assert::AreEqual(size_t(0), spec->features.size()); - Assert::AreEqual("", spec->triplet.c_str()); - } - - TEST_METHOD(parsed_specifier_from_string_with_triplet) - { - auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib:x64-uwp"); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); - auto spec = maybe_spec.get(); - Assert::AreEqual("zlib", spec->name.c_str()); - Assert::AreEqual("x64-uwp", spec->triplet.c_str()); - } - - TEST_METHOD(parsed_specifier_from_string_with_colons) - { - auto ec = vcpkg::ParsedSpecifier::from_string("zlib:x86-uwp:").error(); - Assert::AreEqual(vcpkg::PackageSpecParseResult::TOO_MANY_COLONS, ec); - } - - TEST_METHOD(parsed_specifier_from_string_with_feature) - { - auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[feature]:x64-uwp"); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); - auto spec = maybe_spec.get(); - Assert::AreEqual("zlib", spec->name.c_str()); - Assert::IsTrue(spec->features.size() == 1); - Assert::AreEqual("feature", spec->features.front().c_str()); - Assert::AreEqual("x64-uwp", spec->triplet.c_str()); - } - - TEST_METHOD(parsed_specifier_from_string_with_many_features) - { - auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[0, 1,2]"); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); - auto spec = maybe_spec.get(); - Assert::AreEqual("zlib", spec->name.c_str()); - Assert::IsTrue(spec->features.size() == 3); - Assert::AreEqual("0", spec->features[0].c_str()); - Assert::AreEqual("1", spec->features[1].c_str()); - Assert::AreEqual("2", spec->features[2].c_str()); - Assert::AreEqual("", spec->triplet.c_str()); - } - - TEST_METHOD(parsed_specifier_wildcard_feature) - { - auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[*]"); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); - auto spec = maybe_spec.get(); - Assert::AreEqual("zlib", spec->name.c_str()); - Assert::IsTrue(spec->features.size() == 1); - Assert::AreEqual("*", spec->features[0].c_str()); - Assert::AreEqual("", spec->triplet.c_str()); - } - - TEST_METHOD(expand_wildcards) - { - auto zlib = - vcpkg::FullPackageSpec::from_string("zlib[0,1]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); - auto openssl = - vcpkg::FullPackageSpec::from_string("openssl[*]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); - auto specs = FullPackageSpec::to_feature_specs({zlib, openssl}); - Util::sort(specs); - auto spectargets = FeatureSpec::from_strings_and_triplet( - { - "openssl", - "zlib", - "openssl[*]", - "zlib[0]", - "zlib[1]", - }, - Triplet::X86_UWP); - Util::sort(spectargets); - Assert::IsTrue(specs.size() == spectargets.size()); - Assert::IsTrue(Util::all_equal(specs, spectargets)); - } - - TEST_METHOD(utf8_to_utf16) - { - auto str = vcpkg::Strings::to_utf16("abc"); - Assert::AreEqual(L"abc", str.c_str()); - } - - TEST_METHOD(utf8_to_utf16_with_whitespace) - { - auto str = vcpkg::Strings::to_utf16("abc -x86-windows"); - Assert::AreEqual(L"abc -x86-windows", str.c_str()); - } - }; - - TEST_CLASS(Metrics){}; -} diff --git a/toolsrc/src/tests.paragraph.cpp b/toolsrc/src/tests.paragraph.cpp deleted file mode 100644 index e99d07694..000000000 --- a/toolsrc/src/tests.paragraph.cpp +++ /dev/null @@ -1,441 +0,0 @@ -#include "tests.pch.h" - -#if defined(_WIN32) -#pragma comment(lib, "version") -#pragma comment(lib, "winhttp") -#endif - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -namespace Strings = vcpkg::Strings; - -namespace UnitTest1 -{ - class ControlParsing : public TestClass<ControlParsing> - { - TEST_METHOD(SourceParagraph_Construct_Minimum) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - }}); - - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual("zlib", pgh->core_paragraph->name.c_str()); - Assert::AreEqual("1.2.8", pgh->core_paragraph->version.c_str()); - Assert::AreEqual("", pgh->core_paragraph->maintainer.c_str()); - Assert::AreEqual("", pgh->core_paragraph->description.c_str()); - Assert::AreEqual(size_t(0), pgh->core_paragraph->depends.size()); - } - - TEST_METHOD(SourceParagraph_Construct_Maximum) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "s"}, - {"Version", "v"}, - {"Maintainer", "m"}, - {"Description", "d"}, - {"Build-Depends", "bd"}, - {"Default-Features", "df"}, - {"Supports", "x64"}, - }}); - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual("s", pgh->core_paragraph->name.c_str()); - Assert::AreEqual("v", pgh->core_paragraph->version.c_str()); - Assert::AreEqual("m", pgh->core_paragraph->maintainer.c_str()); - Assert::AreEqual("d", pgh->core_paragraph->description.c_str()); - Assert::AreEqual(size_t(1), pgh->core_paragraph->depends.size()); - Assert::AreEqual("bd", pgh->core_paragraph->depends[0].name().c_str()); - Assert::AreEqual(size_t(1), pgh->core_paragraph->default_features.size()); - Assert::AreEqual("df", pgh->core_paragraph->default_features[0].c_str()); - Assert::AreEqual(size_t(1), pgh->core_paragraph->supports.size()); - Assert::AreEqual("x64", pgh->core_paragraph->supports[0].c_str()); - } - - TEST_METHOD(SourceParagraph_Two_Depends) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Build-Depends", "z, openssl"}, - }}); - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual(size_t(2), pgh->core_paragraph->depends.size()); - Assert::AreEqual("z", pgh->core_paragraph->depends[0].name().c_str()); - Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name().c_str()); - } - - TEST_METHOD(SourceParagraph_Three_Depends) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Build-Depends", "z, openssl, xyz"}, - }}); - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual(size_t(3), pgh->core_paragraph->depends.size()); - Assert::AreEqual("z", pgh->core_paragraph->depends[0].name().c_str()); - Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name().c_str()); - Assert::AreEqual("xyz", pgh->core_paragraph->depends[2].name().c_str()); - } - - TEST_METHOD(SourceParagraph_Three_Supports) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Supports", "x64, windows, uwp"}, - }}); - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual(size_t(3), pgh->core_paragraph->supports.size()); - Assert::AreEqual("x64", pgh->core_paragraph->supports[0].c_str()); - Assert::AreEqual("windows", pgh->core_paragraph->supports[1].c_str()); - Assert::AreEqual("uwp", pgh->core_paragraph->supports[2].c_str()); - } - - TEST_METHOD(SourceParagraph_Construct_Qualified_Depends) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "zlib"}, - {"Version", "1.2.8"}, - {"Build-Depends", "libA (windows), libB (uwp)"}, - }}); - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual("zlib", pgh->core_paragraph->name.c_str()); - Assert::AreEqual("1.2.8", pgh->core_paragraph->version.c_str()); - Assert::AreEqual("", pgh->core_paragraph->maintainer.c_str()); - Assert::AreEqual("", pgh->core_paragraph->description.c_str()); - Assert::AreEqual(size_t(2), pgh->core_paragraph->depends.size()); - Assert::AreEqual("libA", pgh->core_paragraph->depends[0].name().c_str()); - Assert::AreEqual("windows", pgh->core_paragraph->depends[0].qualifier.c_str()); - Assert::AreEqual("libB", pgh->core_paragraph->depends[1].name().c_str()); - Assert::AreEqual("uwp", pgh->core_paragraph->depends[1].qualifier.c_str()); - } - - TEST_METHOD(SourceParagraph_Default_Features) - { - auto m_pgh = - vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ - {"Source", "a"}, - {"Version", "1.0"}, - {"Default-Features", "a1"}, - }}); - Assert::IsTrue(m_pgh.has_value()); - auto& pgh = *m_pgh.get(); - - Assert::AreEqual(size_t(1), pgh->core_paragraph->default_features.size()); - Assert::AreEqual("a1", pgh->core_paragraph->default_features[0].c_str()); - } - - TEST_METHOD(BinaryParagraph_Construct_Minimum) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - }); - - Assert::AreEqual("zlib", pgh.spec.name().c_str()); - Assert::AreEqual("1.2.8", pgh.version.c_str()); - Assert::AreEqual("", pgh.maintainer.c_str()); - Assert::AreEqual("", pgh.description.c_str()); - Assert::AreEqual("x86-windows", pgh.spec.triplet().canonical_name().c_str()); - Assert::AreEqual(size_t(0), pgh.depends.size()); - } - - TEST_METHOD(BinaryParagraph_Construct_Maximum) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "s"}, - {"Version", "v"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Maintainer", "m"}, - {"Description", "d"}, - {"Depends", "bd"}, - }); - Assert::AreEqual("s", pgh.spec.name().c_str()); - Assert::AreEqual("v", pgh.version.c_str()); - Assert::AreEqual("m", pgh.maintainer.c_str()); - Assert::AreEqual("d", pgh.description.c_str()); - Assert::AreEqual(size_t(1), pgh.depends.size()); - Assert::AreEqual("bd", pgh.depends[0].c_str()); - } - - TEST_METHOD(BinaryParagraph_Three_Depends) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", "a, b, c"}, - }); - - Assert::AreEqual(size_t(3), pgh.depends.size()); - Assert::AreEqual("a", pgh.depends[0].c_str()); - Assert::AreEqual("b", pgh.depends[1].c_str()); - Assert::AreEqual("c", pgh.depends[2].c_str()); - } - - TEST_METHOD(BinaryParagraph_Abi) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Abi", "abcd123"}, - }); - - Assert::AreEqual(size_t(0), pgh.depends.size()); - Assert::IsTrue(pgh.abi == "abcd123"); - } - - TEST_METHOD(BinaryParagraph_Default_Features) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "a"}, - {"Version", "1.0"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Default-Features", "a1"}, - }); - - Assert::AreEqual(size_t(0), pgh.depends.size()); - Assert::AreEqual(size_t(1), pgh.default_features.size()); - Assert::IsTrue(pgh.default_features[0] == "a1"); - } - - TEST_METHOD(parse_paragraphs_empty) - { - const char* str = ""; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::IsTrue(pghs.empty()); - } - - TEST_METHOD(parse_paragraphs_one_field) - { - const char* str = "f1: v1"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual(size_t(1), pghs[0].size()); - Assert::AreEqual("v1", pghs[0]["f1"].c_str()); - } - - TEST_METHOD(parse_paragraphs_one_pgh) - { - const char* str = "f1: v1\n" - "f2: v2"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual(size_t(2), pghs[0].size()); - Assert::AreEqual("v1", pghs[0]["f1"].c_str()); - Assert::AreEqual("v2", pghs[0]["f2"].c_str()); - } - - TEST_METHOD(parse_paragraphs_two_pgh) - { - const char* str = "f1: v1\n" - "f2: v2\n" - "\n" - "f3: v3\n" - "f4: v4"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(2), pghs.size()); - Assert::AreEqual(size_t(2), pghs[0].size()); - Assert::AreEqual("v1", pghs[0]["f1"].c_str()); - Assert::AreEqual("v2", pghs[0]["f2"].c_str()); - Assert::AreEqual(size_t(2), pghs[1].size()); - Assert::AreEqual("v3", pghs[1]["f3"].c_str()); - Assert::AreEqual("v4", pghs[1]["f4"].c_str()); - } - - TEST_METHOD(parse_paragraphs_field_names) - { - const char* str = "1:\n" - "f:\n" - "F:\n" - "0:\n" - "F-2:\n"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual(size_t(5), pghs[0].size()); - } - - TEST_METHOD(parse_paragraphs_multiple_blank_lines) - { - const char* str = "f1: v1\n" - "f2: v2\n" - "\n" - "\n" - "f3: v3\n" - "f4: v4"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(2), pghs.size()); - } - - TEST_METHOD(parse_paragraphs_empty_fields) - { - const char* str = "f1:\n" - "f2: "; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual(size_t(2), pghs[0].size()); - Assert::AreEqual("", pghs[0]["f1"].c_str()); - Assert::AreEqual("", pghs[0]["f2"].c_str()); - Assert::AreEqual(size_t(2), pghs[0].size()); - } - - TEST_METHOD(parse_paragraphs_multiline_fields) - { - const char* str = "f1: simple\n" - " f1\r\n" - "f2:\r\n" - " f2\r\n" - " continue\r\n"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual("simple\n f1", pghs[0]["f1"].c_str()); - Assert::AreEqual("\n f2\n continue", pghs[0]["f2"].c_str()); - } - - TEST_METHOD(parse_paragraphs_crlfs) - { - const char* str = "f1: v1\r\n" - "f2: v2\r\n" - "\r\n" - "f3: v3\r\n" - "f4: v4"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(2), pghs.size()); - Assert::AreEqual(size_t(2), pghs[0].size()); - Assert::AreEqual("v1", pghs[0]["f1"].c_str()); - Assert::AreEqual("v2", pghs[0]["f2"].c_str()); - Assert::AreEqual(size_t(2), pghs[1].size()); - Assert::AreEqual("v3", pghs[1]["f3"].c_str()); - Assert::AreEqual("v4", pghs[1]["f4"].c_str()); - } - - TEST_METHOD(parse_paragraphs_comment) - { - const char* str = "f1: v1\r\n" - "#comment\r\n" - "f2: v2\r\n" - "#comment\r\n" - "\r\n" - "#comment\r\n" - "f3: v3\r\n" - "#comment\r\n" - "f4: v4"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(2), pghs.size()); - Assert::AreEqual(size_t(2), pghs[0].size()); - Assert::AreEqual("v1", pghs[0]["f1"].c_str()); - Assert::AreEqual("v2", pghs[0]["f2"].c_str()); - Assert::AreEqual(size_t(2), pghs[1].size()); - Assert::AreEqual("v3", pghs[1]["f3"].c_str()); - Assert::AreEqual("v4", pghs[1]["f4"].c_str()); - } - - TEST_METHOD(parse_comment_before_single_slashN) - { - const char* str = "f1: v1\r\n" - "#comment\n"; - auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs[0].size()); - Assert::AreEqual("v1", pghs[0]["f1"].c_str()); - } - - TEST_METHOD(BinaryParagraph_serialize_min) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - }); - std::string ss = Strings::serialize(pgh); - auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual(size_t(4), pghs[0].size()); - Assert::AreEqual("zlib", pghs[0]["Package"].c_str()); - Assert::AreEqual("1.2.8", pghs[0]["Version"].c_str()); - Assert::AreEqual("x86-windows", pghs[0]["Architecture"].c_str()); - Assert::AreEqual("same", pghs[0]["Multi-Arch"].c_str()); - } - - TEST_METHOD(BinaryParagraph_serialize_max) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Description", "first line\n second line"}, - {"Maintainer", "abc <abc@abc.abc>"}, - {"Depends", "dep"}, - {"Multi-Arch", "same"}, - }); - std::string ss = Strings::serialize(pgh); - auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual(size_t(7), pghs[0].size()); - Assert::AreEqual("zlib", pghs[0]["Package"].c_str()); - Assert::AreEqual("1.2.8", pghs[0]["Version"].c_str()); - Assert::AreEqual("x86-windows", pghs[0]["Architecture"].c_str()); - Assert::AreEqual("same", pghs[0]["Multi-Arch"].c_str()); - Assert::AreEqual("first line\n second line", pghs[0]["Description"].c_str()); - Assert::AreEqual("dep", pghs[0]["Depends"].c_str()); - } - - TEST_METHOD(BinaryParagraph_serialize_multiple_deps) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", "a, b, c"}, - }); - std::string ss = Strings::serialize(pgh); - auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual("a, b, c", pghs[0]["Depends"].c_str()); - } - - TEST_METHOD(BinaryParagraph_serialize_abi) - { - vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, - {"Version", "1.2.8"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Depends", "a, b, c"}, - {"Abi", "123abc"}, - }); - std::string ss = Strings::serialize(pgh); - auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(size_t(1), pghs.size()); - Assert::AreEqual("123abc", pghs[0]["Abi"].c_str()); - } - }; -} diff --git a/toolsrc/src/tests.pch.cpp b/toolsrc/src/tests.pch.cpp deleted file mode 100644 index bdddab76a..000000000 --- a/toolsrc/src/tests.pch.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "tests.pch.h"
diff --git a/toolsrc/src/tests.plan.cpp b/toolsrc/src/tests.plan.cpp deleted file mode 100644 index 238aa7032..000000000 --- a/toolsrc/src/tests.plan.cpp +++ /dev/null @@ -1,1258 +0,0 @@ -#include "tests.pch.h" - -#include <tests.utils.h> - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using namespace vcpkg; - -namespace UnitTest1 -{ - static std::unique_ptr<SourceControlFile> make_control_file( - const char* name, - const char* depends, - const std::vector<std::pair<const char*, const char*>>& features = {}, - const std::vector<const char*>& default_features = {}) - { - using Pgh = std::unordered_map<std::string, std::string>; - std::vector<Pgh> scf_pghs; - scf_pghs.push_back(Pgh{{"Source", name}, - {"Version", "0"}, - {"Build-Depends", depends}, - {"Default-Features", Strings::join(", ", default_features)}}); - for (auto&& feature : features) - { - scf_pghs.push_back(Pgh{ - {"Feature", feature.first}, - {"Description", "feature"}, - {"Build-Depends", feature.second}, - }); - } - auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(scf_pghs)); - Assert::IsTrue(m_pgh.has_value()); - return std::move(*m_pgh.get()); - } - - /// <summary> - /// Assert that the given action an install of given features from given package. - /// </summary> - static void features_check(Dependencies::AnyAction* install_action, - std::string pkg_name, - std::vector<std::string> vec, - const Triplet& triplet = Triplet::X86_WINDOWS) - { - Assert::IsTrue(install_action->install_action.has_value()); - const auto& plan = install_action->install_action.value_or_exit(VCPKG_LINE_INFO); - const auto& feature_list = plan.feature_list; - - Assert::AreEqual(plan.spec.triplet().to_string().c_str(), triplet.to_string().c_str()); - - Assert::AreEqual(pkg_name.c_str(), plan.source_control_file.get()->core_paragraph->name.c_str()); - Assert::AreEqual(size_t(vec.size()), feature_list.size()); - - for (auto&& feature_name : vec) - { - if (feature_name == "core" || feature_name == "") - { - Assert::IsTrue(Util::find(feature_list, "core") != feature_list.end() || - Util::find(feature_list, "") != feature_list.end()); - continue; - } - Assert::IsTrue(Util::find(feature_list, feature_name) != feature_list.end()); - } - } - - /// <summary> - /// Assert that the given action is a remove of given package. - /// </summary> - static void remove_plan_check(Dependencies::AnyAction* remove_action, - std::string pkg_name, - const Triplet& triplet = Triplet::X86_WINDOWS) - { - const auto& plan = remove_action->remove_action.value_or_exit(VCPKG_LINE_INFO); - Assert::AreEqual(plan.spec.triplet().to_string().c_str(), triplet.to_string().c_str()); - Assert::AreEqual(pkg_name.c_str(), plan.spec.name().c_str()); - } - - /// <summary> - /// Map of source control files by their package name. - /// </summary> - struct PackageSpecMap - { - std::unordered_map<std::string, SourceControlFile> map; - Triplet triplet; - PackageSpecMap(const Triplet& t = Triplet::X86_WINDOWS) noexcept { triplet = t; } - - PackageSpec emplace(const char* name, - const char* depends = "", - const std::vector<std::pair<const char*, const char*>>& features = {}, - const std::vector<const char*>& default_features = {}) - { - return emplace(std::move(*make_control_file(name, depends, features, default_features))); - } - PackageSpec emplace(vcpkg::SourceControlFile&& scf) - { - auto spec = PackageSpec::from_name_and_triplet(scf.core_paragraph->name, triplet); - Assert::IsTrue(spec.has_value()); - map.emplace(scf.core_paragraph->name, std::move(scf)); - return PackageSpec{*spec.get()}; - } - }; - - class InstallPlanTests : public TestClass<InstallPlanTests> - { - TEST_METHOD(basic_install_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - 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"); - - Dependencies::MapPortFileProvider map_port(spec_map.map); - auto install_plan = Dependencies::create_feature_install_plan( - map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(3), install_plan.size()); - Assert::AreEqual("c", install_plan[0].spec().name().c_str()); - Assert::AreEqual("b", install_plan[1].spec().name().c_str()); - Assert::AreEqual("a", install_plan[2].spec().name().c_str()); - } - - TEST_METHOD(multiple_install_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - 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"); - auto spec_d = spec_map.emplace("d", "f, g, h"); - auto spec_e = spec_map.emplace("e", "g"); - auto spec_f = spec_map.emplace("f"); - auto spec_g = spec_map.emplace("g"); - auto spec_h = spec_map.emplace("h"); - - Dependencies::MapPortFileProvider map_port(spec_map.map); - auto install_plan = Dependencies::create_feature_install_plan( - map_port, - {FeatureSpec{spec_a, ""}, FeatureSpec{spec_b, ""}, FeatureSpec{spec_c, ""}}, - StatusParagraphs(std::move(status_paragraphs))); - - auto iterator_pos = [&](const PackageSpec& spec) -> int { - auto it = std::find_if( - install_plan.begin(), install_plan.end(), [&](auto& action) { return action.spec() == spec; }); - Assert::IsTrue(it != install_plan.end()); - return (int)(it - install_plan.begin()); - }; - - int a_pos = iterator_pos(spec_a), b_pos = iterator_pos(spec_b), c_pos = iterator_pos(spec_c), - d_pos = iterator_pos(spec_d), e_pos = iterator_pos(spec_e), f_pos = iterator_pos(spec_f), - g_pos = iterator_pos(spec_g), h_pos = iterator_pos(spec_h); - - Assert::IsTrue(a_pos > d_pos); - Assert::IsTrue(b_pos > e_pos); - Assert::IsTrue(b_pos > d_pos); - Assert::IsTrue(c_pos > e_pos); - Assert::IsTrue(c_pos > h_pos); - Assert::IsTrue(d_pos > f_pos); - Assert::IsTrue(d_pos > g_pos); - Assert::IsTrue(d_pos > h_pos); - Assert::IsTrue(e_pos > g_pos); - } - - TEST_METHOD(existing_package_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - - PackageSpecMap spec_map; - auto spec_a = FullPackageSpec{spec_map.emplace("a")}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_a}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(1), install_plan.size()); - auto p = install_plan[0].install_action.get(); - Assert::IsNotNull(p); - Assert::AreEqual("a", p->spec.name().c_str()); - Assert::AreEqual(Dependencies::InstallPlanType::ALREADY_INSTALLED, p->plan_type); - Assert::AreEqual(Dependencies::RequestType::USER_REQUESTED, p->request_type); - } - - TEST_METHOD(user_requested_package_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - PackageSpecMap spec_map; - auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")}; - auto spec_b = FullPackageSpec{spec_map.emplace("b")}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_a}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(2), install_plan.size()); - auto p = install_plan[0].install_action.get(); - Assert::IsNotNull(p); - Assert::AreEqual("b", p->spec.name().c_str()); - Assert::AreEqual(Dependencies::InstallPlanType::BUILD_AND_INSTALL, p->plan_type); - Assert::AreEqual(Dependencies::RequestType::AUTO_SELECTED, p->request_type); - - auto p2 = install_plan[1].install_action.get(); - Assert::IsNotNull(p2); - Assert::AreEqual("a", p2->spec.name().c_str()); - Assert::AreEqual(Dependencies::InstallPlanType::BUILD_AND_INSTALL, p2->plan_type); - Assert::AreEqual(Dependencies::RequestType::USER_REQUESTED, p2->request_type); - } - - TEST_METHOD(long_install_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("j", "k")); - status_paragraphs.push_back(make_status_pgh("k")); - - 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"); - auto spec_c = spec_map.emplace("c", "d, e, f, g, h, j, k"); - auto spec_d = spec_map.emplace("d", "e, f, g, h, j, k"); - auto spec_e = spec_map.emplace("e", "f, g, h, j, k"); - auto spec_f = spec_map.emplace("f", "g, h, j, k"); - auto spec_g = spec_map.emplace("g", "h, j, k"); - auto spec_h = spec_map.emplace("h", "j, k"); - auto spec_j = spec_map.emplace("j", "k"); - auto spec_k = spec_map.emplace("k"); - - Dependencies::MapPortFileProvider map_port(spec_map.map); - auto install_plan = Dependencies::create_feature_install_plan( - map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(8), install_plan.size()); - Assert::AreEqual("h", install_plan[0].spec().name().c_str()); - Assert::AreEqual("g", install_plan[1].spec().name().c_str()); - Assert::AreEqual("f", install_plan[2].spec().name().c_str()); - Assert::AreEqual("e", install_plan[3].spec().name().c_str()); - Assert::AreEqual("d", install_plan[4].spec().name().c_str()); - Assert::AreEqual("c", install_plan[5].spec().name().c_str()); - Assert::AreEqual("b", install_plan[6].spec().name().c_str()); - Assert::AreEqual("a", install_plan[7].spec().name().c_str()); - } - - TEST_METHOD(basic_feature_test_1) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a", "b, b[b1]")); - status_paragraphs.push_back(make_status_pgh("b")); - status_paragraphs.push_back(make_status_feature_pgh("b", "b1")); - - PackageSpecMap spec_map; - auto spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1]", {{"a1", "b[b2]"}}), {"a1"}}; - auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_a}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(4), install_plan.size()); - remove_plan_check(&install_plan[0], "a"); - remove_plan_check(&install_plan[1], "b"); - features_check(&install_plan[2], "b", {"b1", "core", "b1"}); - features_check(&install_plan[3], "a", {"a1", "core"}); - } - - TEST_METHOD(basic_feature_test_2) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - 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", ""}})}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_a}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(2), install_plan.size()); - features_check(&install_plan[0], "b", {"b1", "b2", "core"}); - features_check(&install_plan[1], "a", {"a1", "core"}); - } - - TEST_METHOD(basic_feature_test_3) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - - PackageSpecMap spec_map; - - auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; - auto spec_b = FullPackageSpec{spec_map.emplace("b")}; - auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_c, spec_a}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(4), install_plan.size()); - remove_plan_check(&install_plan[0], "a"); - features_check(&install_plan[1], "b", {"core"}); - features_check(&install_plan[2], "a", {"a1", "core"}); - features_check(&install_plan[3], "c", {"core"}); - } - - TEST_METHOD(basic_feature_test_4) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.push_back(make_status_feature_pgh("a", "a1", "")); - - PackageSpecMap spec_map; - - auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}})}; - auto spec_b = FullPackageSpec{spec_map.emplace("b")}; - auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_c}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(1), install_plan.size()); - features_check(&install_plan[0], "c", {"core"}); - } - - TEST_METHOD(basic_feature_test_5) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - PackageSpecMap spec_map; - - auto spec_a = - FullPackageSpec{spec_map.emplace("a", "", {{"a1", "b[b1]"}, {"a2", "b[b2]"}, {"a3", "a[a2]"}}), {"a3"}}; - auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}})}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_a}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(2), install_plan.size()); - features_check(&install_plan[0], "b", {"core", "b2"}); - features_check(&install_plan[1], "a", {"core", "a3", "a2"}); - } - - TEST_METHOD(basic_feature_test_6) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("b")); - - PackageSpecMap spec_map; - auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[core]"), {"core"}}; - auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_a, spec_b}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(3), install_plan.size()); - remove_plan_check(&install_plan[0], "b"); - features_check(&install_plan[1], "b", {"core", "b1"}); - features_check(&install_plan[2], "a", {"core"}); - } - - TEST_METHOD(basic_feature_test_7) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("x", "b")); - status_paragraphs.push_back(make_status_pgh("b")); - - PackageSpecMap spec_map; - - auto spec_a = FullPackageSpec{spec_map.emplace("a")}; - auto spec_x = FullPackageSpec{spec_map.emplace("x", "a"), {"core"}}; - auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; - - auto install_plan = - Dependencies::create_feature_install_plan(spec_map.map, - FullPackageSpec::to_feature_specs({spec_b}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::AreEqual(size_t(5), install_plan.size()); - remove_plan_check(&install_plan[0], "x"); - remove_plan_check(&install_plan[1], "b"); - - // TODO: order here may change but A < X, and B anywhere - features_check(&install_plan[2], "b", {"core", "b1"}); - features_check(&install_plan[3], "a", {"core"}); - features_check(&install_plan[4], "x", {"core"}); - } - - TEST_METHOD(basic_feature_test_8) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.back()->package.spec = - PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); - - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; - auto spec_b_64 = FullPackageSpec{spec_map.emplace("b")}; - auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; - - spec_map.triplet = Triplet::X86_WINDOWS; - auto spec_a_86 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; - auto spec_b_86 = FullPackageSpec{spec_map.emplace("b")}; - auto spec_c_86 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; - - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({spec_c_64, spec_a_86, spec_a_64, spec_c_86}), - StatusParagraphs(std::move(status_paragraphs))); - - remove_plan_check(&install_plan[0], "a", Triplet::X64_WINDOWS); - remove_plan_check(&install_plan[1], "a"); - features_check(&install_plan[2], "b", {"core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[3], "a", {"a1", "core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[4], "c", {"core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[5], "b", {"core"}); - features_check(&install_plan[6], "a", {"a1", "core"}); - features_check(&install_plan[7], "c", {"core"}); - } - - TEST_METHOD(install_all_features_test) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}), {"core"}}; - - auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); - Assert::IsTrue(install_specs.has_value()); - if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::IsTrue(install_plan.size() == 1); - features_check(&install_plan[0], "a", {"0", "1", "core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(install_default_features_test_1) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - // Add a port "a" with default features "1" and features "0" and "1". - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"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))); - - // Expect the default feature "1" to be installed, but not "0" - Assert::IsTrue(install_plan.size() == 1); - features_check(&install_plan[0], "a", {"1", "core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(install_default_features_test_2) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.back()->package.spec = - PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); - - // Add a port "a" of which "core" is already installed, but we will - // install the default features "explicitly" - // "a" has two features, of which "a1" is default. - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); - - // 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))); - - // Expect "a" to get removed for rebuild and then installed with default - // features. - Assert::IsTrue(install_plan.size() == 2); - remove_plan_check(&install_plan[0], "a", Triplet::X64_WINDOWS); - features_check(&install_plan[1], "a", {"a1", "core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(install_default_features_test_3) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - // "a" has two features, of which "a1" is default. - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); - - // Explicitly install "a" without default features - auto install_specs = FullPackageSpec::from_string("a[core]", Triplet::X64_WINDOWS); - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); - - // Expect the default feature not to get installed. - Assert::IsTrue(install_plan.size() == 1); - features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(install_default_features_of_dependency_test_1) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - // Add a port "a" which depends on the core of "b" - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - spec_map.emplace("a", "b[core]"); - // "b" has two features, of which "b1" is default. - spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); - - // Install "a" (without explicit feature specification) - auto 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))); - - // Expect "a" to get installed and defaults of "b" through the dependency, - // as no explicit features of "b" are installed by the user. - Assert::IsTrue(install_plan.size() == 2); - features_check(&install_plan[0], "b", {"b1", "core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[1], "a", {"core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(do_not_install_default_features_of_existing_dependency) - { - // Add a port "a" which depends on the core of "b" - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - spec_map.emplace("a", "b[core]"); - // "b" has two features, of which "b1" is default. - spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); - - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - // "b[core]" is already installed - status_paragraphs.push_back(make_status_pgh("b")); - status_paragraphs.back()->package.spec = - PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); - - // 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))); - - // Expect "a" to get installed, but not require rebuilding "b" - Assert::IsTrue(install_plan.size() == 1); - features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(install_default_features_of_dependency_test_2) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("b")); - status_paragraphs.back()->package.spec = - PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); - - // 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", "b[core]"); - // "b" has two features, of which "b1" is default. - spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); - - // Install "a" (without explicit feature specification) - auto 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))); - - // Expect "a" to get installed, not the defaults of "b", as the required - // dependencies are already there, installed explicitly by the user. - Assert::IsTrue(install_plan.size() == 1); - 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")); - StatusParagraphs status_db(std::move(pghs)); - - // Add a port "a" of which "core" and "0" are already installed. - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - auto plan = graph.serialize(); - - // The upgrade should not install the default feature - Assert::AreEqual(size_t(2), plan.size()); - - Assert::AreEqual("a", plan[0].spec().name().c_str()); - 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; - // 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)); - - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - auto spec_a = spec_map.emplace("a", "b[core]"); - spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"}); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - auto plan = graph.serialize(); - - // 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); - } - - TEST_METHOD(upgrade_with_new_default_feature) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a", "", "0", "x86-windows")); - - StatusParagraphs status_db(std::move(pghs)); - - 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) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", "b[0]"}}), {"core"}}; - auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", "c[0]"}}), {"core"}}; - auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; - - auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); - Assert::IsTrue(install_specs.has_value()); - if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::IsTrue(install_plan.size() == 3); - features_check(&install_plan[0], "c", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[1], "b", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[2], "a", {"0", "core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(no_transitive_features_test) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", ""}}), {"core"}}; - auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", ""}}), {"core"}}; - auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; - - auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); - Assert::IsTrue(install_specs.has_value()); - if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); - - 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); - features_check(&install_plan[2], "a", {"0", "core"}, Triplet::X64_WINDOWS); - } - - TEST_METHOD(only_transitive_features_test) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - - PackageSpecMap spec_map(Triplet::X64_WINDOWS); - auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", "b[0]"}}), {"core"}}; - auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "", {{"0", "c[0]"}}), {"core"}}; - auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; - - auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); - Assert::IsTrue(install_specs.has_value()); - if (!install_specs.has_value()) return; - auto install_plan = Dependencies::create_feature_install_plan( - spec_map.map, - FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), - StatusParagraphs(std::move(status_paragraphs))); - - Assert::IsTrue(install_plan.size() == 3); - features_check(&install_plan[0], "c", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[1], "b", {"0", "core"}, Triplet::X64_WINDOWS); - features_check(&install_plan[2], "a", {"0", "core"}, Triplet::X64_WINDOWS); - } - }; - - class RemovePlanTests : public TestClass<RemovePlanTests> - { - TEST_METHOD(basic_remove_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); - - Assert::AreEqual(size_t(1), remove_plan.size()); - Assert::AreEqual("a", remove_plan[0].spec.name().c_str()); - } - - TEST_METHOD(recurse_remove_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_pgh("b", "a")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); - - Assert::AreEqual(size_t(2), remove_plan.size()); - Assert::AreEqual("b", remove_plan[0].spec.name().c_str()); - Assert::AreEqual("a", remove_plan[1].spec.name().c_str()); - } - - TEST_METHOD(features_depend_remove_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_pgh("b")); - pghs.push_back(make_status_feature_pgh("b", "0", "a")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); - - Assert::AreEqual(size_t(2), remove_plan.size()); - Assert::AreEqual("b", remove_plan[0].spec.name().c_str()); - Assert::AreEqual("a", remove_plan[1].spec.name().c_str()); - } - - TEST_METHOD(features_depend_remove_scheme_once_removed) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("expat")); - pghs.push_back(make_status_pgh("vtk", "expat")); - pghs.push_back(make_status_pgh("opencv")); - pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("expat")}, status_db); - - Assert::AreEqual(size_t(3), remove_plan.size()); - Assert::AreEqual("opencv", remove_plan[0].spec.name().c_str()); - Assert::AreEqual("vtk", remove_plan[1].spec.name().c_str()); - Assert::AreEqual("expat", remove_plan[2].spec.name().c_str()); - } - - TEST_METHOD(features_depend_remove_scheme_once_removed_x64) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("expat", "", "", "x64")); - pghs.push_back(make_status_pgh("vtk", "expat", "", "x64")); - pghs.push_back(make_status_pgh("opencv", "", "", "x64")); - pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk", "x64")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan( - {unsafe_pspec("expat", Triplet::from_canonical_name("x64"))}, status_db); - - Assert::AreEqual(size_t(3), remove_plan.size()); - Assert::AreEqual("opencv", remove_plan[0].spec.name().c_str()); - Assert::AreEqual("vtk", remove_plan[1].spec.name().c_str()); - Assert::AreEqual("expat", remove_plan[2].spec.name().c_str()); - } - - TEST_METHOD(features_depend_core_remove_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("curl", "", "", "x64")); - pghs.push_back(make_status_pgh("cpr", "curl[core]", "", "x64")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan( - {unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db); - - Assert::AreEqual(size_t(2), remove_plan.size()); - Assert::AreEqual("cpr", remove_plan[0].spec.name().c_str()); - Assert::AreEqual("curl", remove_plan[1].spec.name().c_str()); - } - - TEST_METHOD(features_depend_core_remove_scheme_2) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("curl", "", "", "x64")); - pghs.push_back(make_status_feature_pgh("curl", "a", "", "x64")); - pghs.push_back(make_status_feature_pgh("curl", "b", "curl[a]", "x64")); - StatusParagraphs status_db(std::move(pghs)); - - auto remove_plan = Dependencies::create_remove_plan( - {unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db); - - Assert::AreEqual(size_t(1), remove_plan.size()); - Assert::AreEqual("curl", remove_plan[0].spec.name().c_str()); - } - }; - - class UpgradePlanTests : public TestClass<UpgradePlanTests> - { - TEST_METHOD(basic_upgrade_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - 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()); - } - - TEST_METHOD(basic_upgrade_scheme_with_recurse) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_pgh("b", "a")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - spec_map.emplace("b", "a"); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - Assert::AreEqual(size_t(4), plan.size()); - Assert::AreEqual("b", 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].remove_action.has_value()); - - Assert::AreEqual("a", plan[2].spec().name().c_str()); - Assert::IsTrue(plan[2].install_action.has_value()); - - Assert::AreEqual("b", plan[3].spec().name().c_str()); - Assert::IsTrue(plan[3].install_action.has_value()); - } - - TEST_METHOD(basic_upgrade_scheme_with_bystander) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_pgh("b")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - spec_map.emplace("b", "a"); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - 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()); - } - - TEST_METHOD(basic_upgrade_scheme_with_new_dep) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a", "b"); - spec_map.emplace("b"); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - Assert::AreEqual(size_t(3), plan.size()); - Assert::AreEqual("a", plan[0].spec().name().c_str()); - Assert::IsTrue(plan[0].remove_action.has_value()); - Assert::AreEqual("b", plan[1].spec().name().c_str()); - Assert::IsTrue(plan[1].install_action.has_value()); - Assert::AreEqual("a", plan[2].spec().name().c_str()); - Assert::IsTrue(plan[2].install_action.has_value()); - } - - TEST_METHOD(basic_upgrade_scheme_with_features) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_feature_pgh("a", "a1")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - Assert::AreEqual(size_t(2), plan.size()); - - Assert::AreEqual("a", plan[0].spec().name().c_str()); - Assert::IsTrue(plan[0].remove_action.has_value()); - - features_check(&plan[1], "a", {"core", "a1"}); - } - - TEST_METHOD(basic_upgrade_scheme_with_new_default_feature) - { - // only core of package "a" is installed - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - StatusParagraphs status_db(std::move(pghs)); - - // a1 was added as a default feature and should be installed in upgrade - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"}); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - Assert::AreEqual(size_t(2), plan.size()); - - Assert::AreEqual("a", plan[0].spec().name().c_str()); - Assert::IsTrue(plan[0].remove_action.has_value()); - - features_check(&plan[1], "a", {"core", "a1"}); - } - - TEST_METHOD(basic_upgrade_scheme_with_self_features) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_feature_pgh("a", "a1", "")); - pghs.push_back(make_status_feature_pgh("a", "a2", "a[a1]")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a", "", {{"a1", ""}, {"a2", "a[a1]"}}); - - Dependencies::MapPortFileProvider provider(spec_map.map); - Dependencies::PackageGraph graph(provider, status_db); - - graph.upgrade(spec_a); - - auto plan = graph.serialize(); - - 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()); - Assert::IsTrue(plan[1].install_action.get()->feature_list == std::set<std::string>{"core", "a1", "a2"}); - } - }; - - class ExportPlanTests : public TestClass<ExportPlanTests> - { - TEST_METHOD(basic_export_scheme) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - - auto plan = Dependencies::create_export_plan({spec_a}, status_db); - - Assert::AreEqual(size_t(1), plan.size()); - Assert::AreEqual("a", plan[0].spec.name().c_str()); - Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); - } - - TEST_METHOD(basic_export_scheme_with_recurse) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_pgh("b", "a")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - auto spec_b = spec_map.emplace("b", "a"); - - auto plan = Dependencies::create_export_plan({spec_b}, status_db); - - Assert::AreEqual(size_t(2), plan.size()); - Assert::AreEqual("a", plan[0].spec.name().c_str()); - Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); - - Assert::AreEqual("b", plan[1].spec.name().c_str()); - Assert::IsTrue(plan[1].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); - } - - TEST_METHOD(basic_export_scheme_with_bystander) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_pgh("b")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - auto spec_b = spec_map.emplace("b", "a"); - - auto plan = Dependencies::create_export_plan({spec_a}, status_db); - - Assert::AreEqual(size_t(1), plan.size()); - Assert::AreEqual("a", plan[0].spec.name().c_str()); - Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); - } - - TEST_METHOD(basic_export_scheme_with_missing) - { - StatusParagraphs status_db; - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a"); - - auto plan = Dependencies::create_export_plan({spec_a}, status_db); - - Assert::AreEqual(size_t(1), plan.size()); - Assert::AreEqual("a", plan[0].spec.name().c_str()); - Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::NOT_BUILT); - } - - TEST_METHOD(basic_export_scheme_with_features) - { - std::vector<std::unique_ptr<StatusParagraph>> pghs; - pghs.push_back(make_status_pgh("b")); - pghs.push_back(make_status_pgh("a")); - pghs.push_back(make_status_feature_pgh("a", "a1", "b[core]")); - StatusParagraphs status_db(std::move(pghs)); - - PackageSpecMap spec_map; - auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); - - auto plan = Dependencies::create_export_plan({spec_a}, status_db); - - Assert::AreEqual(size_t(2), plan.size()); - - Assert::AreEqual("b", plan[0].spec.name().c_str()); - Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); - - Assert::AreEqual("a", plan[1].spec.name().c_str()); - Assert::IsTrue(plan[1].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); - } - }; -} diff --git a/toolsrc/src/tests.statusparagraphs.cpp b/toolsrc/src/tests.statusparagraphs.cpp deleted file mode 100644 index fa0d54fac..000000000 --- a/toolsrc/src/tests.statusparagraphs.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "tests.pch.h" - -#include <tests.utils.h> - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using namespace vcpkg; -using namespace vcpkg::Paragraphs; - -namespace UnitTest1 -{ - class StatusParagraphsTests : public TestClass<StatusParagraphsTests> - { - TEST_METHOD(find_installed) - { - auto pghs = parse_paragraphs(R"( -Package: ffmpeg -Version: 3.3.3 -Architecture: x64-windows -Multi-Arch: same -Description: -Status: install ok installed -)"); - Assert::IsTrue(!!pghs); - if (!pghs) return; - - StatusParagraphs status_db(Util::fmap( - *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); - - auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); - Assert::IsTrue(it != status_db.end()); - } - - TEST_METHOD(find_not_installed) - { - auto pghs = parse_paragraphs(R"( -Package: ffmpeg -Version: 3.3.3 -Architecture: x64-windows -Multi-Arch: same -Description: -Status: purge ok not-installed -)"); - Assert::IsTrue(!!pghs); - if (!pghs) return; - - StatusParagraphs status_db(Util::fmap( - *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); - - auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); - Assert::IsTrue(it == status_db.end()); - } - - TEST_METHOD(find_with_feature_packages) - { - auto pghs = parse_paragraphs(R"( -Package: ffmpeg -Version: 3.3.3 -Architecture: x64-windows -Multi-Arch: same -Description: -Status: install ok installed - -Package: ffmpeg -Feature: openssl -Depends: openssl -Architecture: x64-windows -Multi-Arch: same -Description: -Status: purge ok not-installed -)"); - Assert::IsTrue(!!pghs); - if (!pghs) return; - - StatusParagraphs status_db(Util::fmap( - *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); - - auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); - Assert::IsTrue(it != status_db.end()); - - // Feature "openssl" is not installed and should not be found - auto it1 = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"}); - Assert::IsTrue(it1 == status_db.end()); - } - - TEST_METHOD(find_for_feature_packages) - { - auto pghs = parse_paragraphs(R"( -Package: ffmpeg -Version: 3.3.3 -Architecture: x64-windows -Multi-Arch: same -Description: -Status: install ok installed - -Package: ffmpeg -Feature: openssl -Depends: openssl -Architecture: x64-windows -Multi-Arch: same -Description: -Status: install ok installed -)"); - Assert::IsTrue(!!pghs); - if (!pghs) return; - - StatusParagraphs status_db(Util::fmap( - *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); - - // Feature "openssl" is installed and should therefore be found - auto it = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"}); - Assert::IsTrue(it != status_db.end()); - } - }; -} diff --git a/toolsrc/src/tests.update.cpp b/toolsrc/src/tests.update.cpp deleted file mode 100644 index b6e487c17..000000000 --- a/toolsrc/src/tests.update.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include "tests.pch.h" - -#include <tests.utils.h> - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using namespace vcpkg; -using namespace vcpkg::Update; - -namespace UnitTest1 -{ - using Pgh = std::vector<std::unordered_map<std::string, std::string>>; - - class UpdateTests : public TestClass<UpdateTests> - { - TEST_METHOD(find_outdated_packages_basic) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.back()->package.version = "2"; - - StatusParagraphs status_db(std::move(status_paragraphs)); - - std::unordered_map<std::string, SourceControlFile> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); - map.emplace("a", std::move(*scf)); - Dependencies::MapPortFileProvider provider(map); - - auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); - - Assert::AreEqual(size_t(1), pkgs.size()); - Assert::AreEqual("2", pkgs[0].version_diff.left.to_string().c_str()); - Assert::AreEqual("0", pkgs[0].version_diff.right.to_string().c_str()); - } - - TEST_METHOD(find_outdated_packages_features) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.back()->package.version = "2"; - - status_paragraphs.push_back(make_status_feature_pgh("a", "b")); - status_paragraphs.back()->package.version = "2"; - - StatusParagraphs status_db(std::move(status_paragraphs)); - - std::unordered_map<std::string, SourceControlFile> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); - map.emplace("a", std::move(*scf)); - Dependencies::MapPortFileProvider provider(map); - - auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); - - Assert::AreEqual(size_t(1), pkgs.size()); - Assert::AreEqual("2", pkgs[0].version_diff.left.to_string().c_str()); - Assert::AreEqual("0", pkgs[0].version_diff.right.to_string().c_str()); - } - - TEST_METHOD(find_outdated_packages_features_2) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.back()->package.version = "2"; - - status_paragraphs.push_back(make_status_feature_pgh("a", "b")); - status_paragraphs.back()->package.version = "0"; - status_paragraphs.back()->state = InstallState::NOT_INSTALLED; - status_paragraphs.back()->want = Want::PURGE; - - StatusParagraphs status_db(std::move(status_paragraphs)); - - std::unordered_map<std::string, SourceControlFile> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); - map.emplace("a", std::move(*scf)); - Dependencies::MapPortFileProvider provider(map); - - auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); - - Assert::AreEqual(size_t(1), pkgs.size()); - Assert::AreEqual("2", pkgs[0].version_diff.left.to_string().c_str()); - Assert::AreEqual("0", pkgs[0].version_diff.right.to_string().c_str()); - } - - TEST_METHOD(find_outdated_packages_none) - { - std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; - status_paragraphs.push_back(make_status_pgh("a")); - status_paragraphs.back()->package.version = "2"; - - StatusParagraphs status_db(std::move(status_paragraphs)); - - std::unordered_map<std::string, SourceControlFile> map; - auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "2"}}})); - map.emplace("a", std::move(*scf)); - Dependencies::MapPortFileProvider provider(map); - - auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), - &OutdatedPackage::compare_by_name); - - Assert::AreEqual(size_t(0), pkgs.size()); - } - }; -} diff --git a/toolsrc/src/tests.utils.cpp b/toolsrc/src/tests.utils.cpp deleted file mode 100644 index ac391f559..000000000 --- a/toolsrc/src/tests.utils.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "tests.pch.h" - -#include "tests.utils.h" - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; -using namespace vcpkg; - -std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, - const char* depends, - const char* default_features, - const char* triplet) -{ - using Pgh = std::unordered_map<std::string, std::string>; - return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, - {"Version", "1"}, - {"Architecture", triplet}, - {"Multi-Arch", "same"}, - {"Depends", depends}, - {"Default-Features", default_features}, - {"Status", "install ok installed"}}); -} -std::unique_ptr<StatusParagraph> make_status_feature_pgh(const char* name, - const char* feature, - const char* depends, - const char* triplet) -{ - using Pgh = std::unordered_map<std::string, std::string>; - return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, - {"Version", "1"}, - {"Feature", feature}, - {"Architecture", triplet}, - {"Multi-Arch", "same"}, - {"Depends", depends}, - {"Status", "install ok installed"}}); -} - -PackageSpec unsafe_pspec(std::string name, Triplet t) -{ - auto m_ret = PackageSpec::from_name_and_triplet(name, t); - Assert::IsTrue(m_ret.has_value()); - return m_ret.value_or_exit(VCPKG_LINE_INFO); -} diff --git a/toolsrc/src/vcpkg-test/arguments.cpp b/toolsrc/src/vcpkg-test/arguments.cpp new file mode 100644 index 000000000..c63a31396 --- /dev/null +++ b/toolsrc/src/vcpkg-test/arguments.cpp @@ -0,0 +1,109 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/vcpkgcmdarguments.h> + +#include <vector> + +using vcpkg::CommandSetting; +using vcpkg::CommandStructure; +using vcpkg::CommandSwitch; +using vcpkg::VcpkgCmdArguments; + +TEST_CASE ("VcpkgCmdArguments from lowercase argument sequence", "[arguments]") +{ + std::vector<std::string> t = {"--vcpkg-root", + "C:\\vcpkg", + "--x-scripts-root=C:\\scripts", + "--debug", + "--sendmetrics", + "--printmetrics", + "--overlay-ports=C:\\ports1", + "--overlay-ports=C:\\ports2", + "--overlay-triplets=C:\\tripletsA", + "--overlay-triplets=C:\\tripletsB"}; + auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size()); + + REQUIRE(*v.vcpkg_root_dir == "C:\\vcpkg"); + REQUIRE(*v.scripts_root_dir == "C:\\scripts"); + REQUIRE(v.debug); + REQUIRE(*v.debug.get()); + REQUIRE(v.sendmetrics); + REQUIRE(*v.sendmetrics.get()); + REQUIRE(v.printmetrics); + REQUIRE(*v.printmetrics.get()); + + REQUIRE(v.overlay_ports->size() == 2); + REQUIRE(v.overlay_ports->at(0) == "C:\\ports1"); + REQUIRE(v.overlay_ports->at(1) == "C:\\ports2"); + + REQUIRE(v.overlay_triplets->size() == 2); + REQUIRE(v.overlay_triplets->at(0) == "C:\\tripletsA"); + REQUIRE(v.overlay_triplets->at(1) == "C:\\tripletsB"); +} + +TEST_CASE ("VcpkgCmdArguments from uppercase argument sequence", "[arguments]") +{ + std::vector<std::string> t = {"--VCPKG-ROOT", + "C:\\vcpkg", + "--X-SCRIPTS-ROOT=C:\\scripts", + "--DEBUG", + "--SENDMETRICS", + "--PRINTMETRICS", + "--OVERLAY-PORTS=C:\\ports1", + "--OVERLAY-PORTS=C:\\ports2", + "--OVERLAY-TRIPLETS=C:\\tripletsA", + "--OVERLAY-TRIPLETS=C:\\tripletsB"}; + auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size()); + + REQUIRE(*v.vcpkg_root_dir == "C:\\vcpkg"); + REQUIRE(*v.scripts_root_dir == "C:\\scripts"); + REQUIRE(v.debug); + REQUIRE(*v.debug.get()); + REQUIRE(v.sendmetrics); + REQUIRE(*v.sendmetrics.get()); + REQUIRE(v.printmetrics); + REQUIRE(*v.printmetrics.get()); + + REQUIRE(v.overlay_ports->size() == 2); + REQUIRE(v.overlay_ports->at(0) == "C:\\ports1"); + REQUIRE(v.overlay_ports->at(1) == "C:\\ports2"); + + REQUIRE(v.overlay_triplets->size() == 2); + REQUIRE(v.overlay_triplets->at(0) == "C:\\tripletsA"); + REQUIRE(v.overlay_triplets->at(1) == "C:\\tripletsB"); +} + +TEST_CASE ("VcpkgCmdArguments from argument sequence with valued options", "[arguments]") +{ + SECTION ("case 1") + { + std::array<CommandSetting, 1> settings = {{{"--a", ""}}}; + CommandStructure cmdstruct = {"", 0, SIZE_MAX, {{}, settings}, nullptr}; + + std::vector<std::string> t = {"--a=b", "command", "argument"}; + auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size()); + auto opts = v.parse_arguments(cmdstruct); + + REQUIRE(opts.settings["--a"] == "b"); + REQUIRE(v.command_arguments.size() == 1); + REQUIRE(v.command_arguments[0] == "argument"); + REQUIRE(v.command == "command"); + } + + SECTION ("case 2") + { + std::array<CommandSwitch, 2> switches = {{{"--a", ""}, {"--c", ""}}}; + std::array<CommandSetting, 2> settings = {{{"--b", ""}, {"--d", ""}}}; + CommandStructure cmdstruct = {"", 0, SIZE_MAX, {switches, settings}, nullptr}; + + std::vector<std::string> t = {"--a", "--b=c"}; + auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size()); + auto opts = v.parse_arguments(cmdstruct); + + REQUIRE(opts.settings["--b"] == "c"); + REQUIRE(opts.settings.find("--d") == opts.settings.end()); + REQUIRE(opts.switches.find("--a") != opts.switches.end()); + REQUIRE(opts.settings.find("--c") == opts.settings.end()); + REQUIRE(v.command_arguments.size() == 0); + } +} diff --git a/toolsrc/src/vcpkg-test/catch.cpp b/toolsrc/src/vcpkg-test/catch.cpp new file mode 100644 index 000000000..50331c644 --- /dev/null +++ b/toolsrc/src/vcpkg-test/catch.cpp @@ -0,0 +1,11 @@ +#define CATCH_CONFIG_RUNNER +#include <catch2/catch.hpp> + +#include <vcpkg/base/system.debug.h> + +int main(int argc, char** argv) +{ + vcpkg::Debug::g_debugging = true; + + return Catch::Session().run(argc, argv); +} diff --git a/toolsrc/src/vcpkg-test/chrono.cpp b/toolsrc/src/vcpkg-test/chrono.cpp new file mode 100644 index 000000000..fb8a0dee9 --- /dev/null +++ b/toolsrc/src/vcpkg-test/chrono.cpp @@ -0,0 +1,34 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/base/chrono.h> + +namespace Chrono = vcpkg::Chrono; + +TEST_CASE ("parse time", "[chrono]") +{ + auto timestring = "1990-02-03T04:05:06.0Z"; + auto maybe_time = Chrono::CTime::parse(timestring); + + REQUIRE(maybe_time.has_value()); + REQUIRE(maybe_time.get()->to_string() == timestring); +} + +TEST_CASE ("parse blank time", "[chrono]") +{ + auto maybe_time = Chrono::CTime::parse(""); + + REQUIRE_FALSE(maybe_time.has_value()); +} + +TEST_CASE ("difference of times", "[chrono]") +{ + auto maybe_time1 = Chrono::CTime::parse("1990-02-03T04:05:06.0Z"); + auto maybe_time2 = Chrono::CTime::parse("1990-02-10T04:05:06.0Z"); + + REQUIRE(maybe_time1.has_value()); + REQUIRE(maybe_time2.has_value()); + + auto delta = maybe_time2.get()->to_time_point() - maybe_time1.get()->to_time_point(); + + REQUIRE(std::chrono::duration_cast<std::chrono::hours>(delta).count() == 24 * 7); +} diff --git a/toolsrc/src/vcpkg-test/dependencies.cpp b/toolsrc/src/vcpkg-test/dependencies.cpp new file mode 100644 index 000000000..2344bb990 --- /dev/null +++ b/toolsrc/src/vcpkg-test/dependencies.cpp @@ -0,0 +1,28 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/sourceparagraph.h> + +using namespace vcpkg; +using Parse::parse_comma_list; + +TEST_CASE ("parse depends", "[dependencies]") +{ + auto v = expand_qualified_dependencies(parse_comma_list("libA (windows)")); + REQUIRE(v.size() == 1); + REQUIRE(v.at(0).depend.name == "libA"); + REQUIRE(v.at(0).qualifier == "windows"); +} + +TEST_CASE ("filter depends", "[dependencies]") +{ + auto deps = expand_qualified_dependencies(parse_comma_list("libA (windows), libB, libC (uwp)")); + auto v = filter_dependencies(deps, Triplet::X64_WINDOWS); + REQUIRE(v.size() == 2); + REQUIRE(v.at(0) == "libA"); + REQUIRE(v.at(1) == "libB"); + + auto v2 = filter_dependencies(deps, Triplet::ARM_UWP); + REQUIRE(v.size() == 2); + REQUIRE(v2.at(0) == "libB"); + REQUIRE(v2.at(1) == "libC"); +} diff --git a/toolsrc/src/vcpkg-test/files.cpp b/toolsrc/src/vcpkg-test/files.cpp new file mode 100644 index 000000000..d8bc5ba74 --- /dev/null +++ b/toolsrc/src/vcpkg-test/files.cpp @@ -0,0 +1,243 @@ +#include <catch2/catch.hpp> +#include <vcpkg-test/util.h> + +#include <vcpkg/base/files.h> +#include <vcpkg/base/strings.h> + +#include <iostream> +#include <random> + +#include <vector> + +using vcpkg::Test::AllowSymlinks; +using vcpkg::Test::base_temporary_directory; +using vcpkg::Test::can_create_symlinks; + +#define CHECK_EC_ON_FILE(file, ec) \ + do \ + { \ + if (ec) \ + { \ + FAIL(file << ": " << ec.message()); \ + } \ + } while (0) + +namespace +{ + using uid_t = std::uniform_int_distribution<std::uint64_t>; + using urbg_t = std::mt19937_64; + + urbg_t get_urbg(std::uint64_t index) + { + // smallest prime > 2**63 - 1 + return urbg_t{index + 9223372036854775837ULL}; + } + + std::string get_random_filename(urbg_t& urbg) { return vcpkg::Strings::b32_encode(uid_t{}(urbg)); } + + struct MaxDepth + { + std::uint64_t i; + explicit MaxDepth(std::uint64_t i) : i(i) {} + operator uint64_t() const { return i; } + }; + + struct Width + { + std::uint64_t i; + explicit Width(std::uint64_t i) : i(i) {} + operator uint64_t() const { return i; } + }; + + struct CurrentDepth + { + std::uint64_t i; + explicit CurrentDepth(std::uint64_t i) : i(i) {} + operator uint64_t() const { return i; } + CurrentDepth incremented() const { return CurrentDepth{i + 1}; } + }; + + void create_directory_tree(urbg_t& urbg, + vcpkg::Files::Filesystem& fs, + const fs::path& base, + MaxDepth max_depth, + AllowSymlinks allow_symlinks = AllowSymlinks::Yes, + Width width = Width{5}, + CurrentDepth current_depth = CurrentDepth{0}) + { + std::random_device rd; + + // we want ~70% of our "files" to be directories, and then a third + // each of the remaining ~30% to be regular files, directory symlinks, + // and regular symlinks + constexpr std::uint64_t directory_min_tag = 0; + constexpr std::uint64_t directory_max_tag = 6; + constexpr std::uint64_t regular_file_tag = 7; + constexpr std::uint64_t regular_symlink_tag = 8; + constexpr std::uint64_t directory_symlink_tag = 9; + + allow_symlinks = AllowSymlinks{allow_symlinks && can_create_symlinks()}; + + // if we're at the max depth, we only want to build non-directories + std::uint64_t file_type; + if (current_depth >= max_depth) + { + file_type = uid_t{regular_file_tag, directory_symlink_tag}(urbg); + } + else if (current_depth < 2) + { + file_type = directory_min_tag; + } + else + { + file_type = uid_t{directory_min_tag, regular_symlink_tag}(urbg); + } + + if (!allow_symlinks && file_type > regular_file_tag) + { + file_type = regular_file_tag; + } + + std::error_code ec; + if (file_type <= directory_max_tag) + { + fs.create_directory(base, ec); + if (ec) + { + CHECK_EC_ON_FILE(base, ec); + } + + for (std::uint64_t i = 0; i < width; ++i) + { + create_directory_tree(urbg, + fs, + base / get_random_filename(urbg), + max_depth, + allow_symlinks, + width, + current_depth.incremented()); + } + } + else if (file_type == regular_file_tag) + { + // regular file + fs.write_contents(base, "", ec); + } + else if (file_type == regular_symlink_tag) + { + // regular symlink + auto base_link = base; + base_link.replace_filename(base.filename().u8string() + "-orig"); + fs.write_contents(base_link, "", ec); + CHECK_EC_ON_FILE(base_link, ec); + vcpkg::Test::create_symlink(base_link, base, ec); + } + else // type == directory_symlink_tag + { + // directory symlink + auto parent = base; + parent.remove_filename(); + vcpkg::Test::create_directory_symlink(parent, base, ec); + } + + CHECK_EC_ON_FILE(base, ec); + REQUIRE(fs::exists(fs.symlink_status(base, ec))); + CHECK_EC_ON_FILE(base, ec); + } + + vcpkg::Files::Filesystem& setup() + { + auto& fs = vcpkg::Files::get_real_filesystem(); + + std::error_code ec; + fs.create_directory(base_temporary_directory(), ec); + CHECK_EC_ON_FILE(base_temporary_directory(), ec); + + return fs; + } +} + +TEST_CASE ("remove all", "[files]") +{ + auto urbg = get_urbg(0); + + auto& fs = setup(); + + fs::path temp_dir = base_temporary_directory() / get_random_filename(urbg); + INFO("temp dir is: " << temp_dir); + + create_directory_tree(urbg, fs, temp_dir, MaxDepth{5}); + + std::error_code ec; + fs::path fp; + fs.remove_all(temp_dir, ec, fp); + CHECK_EC_ON_FILE(fp, ec); + + REQUIRE_FALSE(fs.exists(temp_dir, ec)); + CHECK_EC_ON_FILE(temp_dir, ec); +} + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +TEST_CASE ("remove all -- benchmarks", "[files][!benchmark]") +{ + auto urbg = get_urbg(1); + auto& fs = setup(); + + struct + { + urbg_t& urbg; + vcpkg::Files::Filesystem& fs; + + void operator()(Catch::Benchmark::Chronometer& meter, MaxDepth max_depth, AllowSymlinks allow_symlinks) const + { + std::vector<fs::path> temp_dirs; + temp_dirs.resize(meter.runs()); + + std::generate(begin(temp_dirs), end(temp_dirs), [&] { + fs::path temp_dir = base_temporary_directory() / get_random_filename(urbg); + create_directory_tree(urbg, fs, temp_dir, max_depth, allow_symlinks); + return temp_dir; + }); + + meter.measure([&](int run) { + std::error_code ec; + fs::path fp; + const auto& temp_dir = temp_dirs[run]; + + fs.remove_all(temp_dir, ec, fp); + CHECK_EC_ON_FILE(fp, ec); + }); + + for (const auto& dir : temp_dirs) + { + std::error_code ec; + REQUIRE_FALSE(fs.exists(dir, ec)); + CHECK_EC_ON_FILE(dir, ec); + } + } + } do_benchmark = {urbg, fs}; + + BENCHMARK_ADVANCED("small directory, no symlinks")(Catch::Benchmark::Chronometer meter) + { + do_benchmark(meter, MaxDepth{2}, AllowSymlinks::No); + }; + + BENCHMARK_ADVANCED("large directory, no symlinks")(Catch::Benchmark::Chronometer meter) + { + do_benchmark(meter, MaxDepth{5}, AllowSymlinks::No); + }; + + if (can_create_symlinks()) + { + BENCHMARK_ADVANCED("small directory, symlinks")(Catch::Benchmark::Chronometer meter) + { + do_benchmark(meter, MaxDepth{2}, AllowSymlinks::Yes); + }; + + BENCHMARK_ADVANCED("large directory, symlinks")(Catch::Benchmark::Chronometer meter) + { + do_benchmark(meter, MaxDepth{5}, AllowSymlinks::Yes); + }; + } +} +#endif diff --git a/toolsrc/src/vcpkg-test/hash.cpp b/toolsrc/src/vcpkg-test/hash.cpp new file mode 100644 index 000000000..9f3ccc25e --- /dev/null +++ b/toolsrc/src/vcpkg-test/hash.cpp @@ -0,0 +1,276 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/base/hash.h> + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <map> + +namespace Hash = vcpkg::Hash; +using vcpkg::StringView; + +// Require algorithm: Hash::Algorithm::Tag to be in scope +#define CHECK_HASH(size, value, real_hash) \ + do \ + { \ + unsigned char data[size]; \ + std::fill(std::begin(data), std::end(data), static_cast<unsigned char>(value)); \ + const auto hash = Hash::get_bytes_hash(data, data + size, algorithm); \ + REQUIRE(hash == real_hash); \ + } while (0) + +#define CHECK_HASH_OF(data, real_hash) \ + do \ + { \ + const auto hash = Hash::get_bytes_hash(std::begin(data), std::end(data), algorithm); \ + REQUIRE(hash == real_hash); \ + } while (0) + +#define CHECK_HASH_STRING(data, real_hash) \ + do \ + { \ + const auto hash = Hash::get_string_hash(data, algorithm); \ + REQUIRE(hash == real_hash); \ + } while (0) + +// Requires hasher: std::unique_ptr<Hash::Hasher> to be in scope +#define CHECK_HASH_LARGE(size, value, real_hash) \ + do \ + { \ + hasher->clear(); \ + std::uint64_t remaining = size; \ + unsigned char buffer[512]; \ + std::fill(std::begin(buffer), std::end(buffer), static_cast<unsigned char>(value)); \ + while (remaining) \ + { \ + if (remaining < 512) \ + { \ + hasher->add_bytes(std::begin(buffer), std::begin(buffer) + remaining); \ + remaining = 0; \ + } \ + else \ + { \ + hasher->add_bytes(std::begin(buffer), std::end(buffer)); \ + remaining -= 512; \ + } \ + } \ + REQUIRE(hasher->get_hash() == real_hash); \ + } while (0) + +TEST_CASE ("SHA1: basic tests", "[hash][sha1]") +{ + const auto algorithm = Hash::Algorithm::Sha1; + + CHECK_HASH_STRING("", "da39a3ee5e6b4b0d3255bfef95601890afd80709"); + CHECK_HASH_STRING(";", "2d14ab97cc3dc294c51c0d6814f4ea45f4b4e312"); + CHECK_HASH_STRING("asdifasdfnas", "b77eb8a1b4c2ef6716d7d302647e4511b1a638a6"); + CHECK_HASH_STRING("asdfanvoinaoifawenflawenfiwnofvnasfjvnaslkdfjlkasjdfanm," + "werflawoienfowanevoinwai32910u2740918741o;j;wejfqwioaher9283hrpf;asd", + "c69bcd30c196c7050906d212722dd7a7659aad04"); +} + +TEST_CASE ("SHA1: NIST test cases (small)", "[hash][sha1]") +{ + const auto algorithm = Hash::Algorithm::Sha1; + + CHECK_HASH_STRING("abc", "a9993e364706816aba3e25717850c26c9cd0d89d"); + CHECK_HASH_STRING("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "84983e441c3bd26ebaae4aa1f95129e5e54670f1"); +} + +TEST_CASE ("SHA256: basic tests", "[hash][sha256]") +{ + const auto algorithm = Hash::Algorithm::Sha256; + + CHECK_HASH_STRING("", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + CHECK_HASH_STRING(";", "41b805ea7ac014e23556e98bb374702a08344268f92489a02f0880849394a1e4"); + CHECK_HASH_STRING("asdifasdfnas", "2bb1fb910831fdc11d5a3996425a84ace27aeb81c9c20ace9f60ac1b3218b291"); + CHECK_HASH_STRING("asdfanvoinaoifawenflawenfiwnofvnasfjvnaslkdfjlkasjdfanm," + "werflawoienfowanevoinwai32910u2740918741o;j;wejfqwioaher9283hrpf;asd", + "10c98034b424d4e40ca933bc524ea38b4e53290d76e8b38edc4ea2fec7f529aa"); +} + +TEST_CASE ("SHA256: NIST test cases (small)", "[hash][sha256]") +{ + const auto algorithm = Hash::Algorithm::Sha256; + + CHECK_HASH(1, 0xbd, "68325720aabd7c82f30f554b313d0570c95accbb7dc4b5aae11204c08ffe732b"); + { + const unsigned char data[] = {0xc9, 0x8c, 0x8e, 0x55}; + CHECK_HASH_OF(data, "7abc22c0ae5af26ce93dbb94433a0e0b2e119d014f8e7f65bd56c61ccccd9504"); + } + CHECK_HASH(55, 0, "02779466cdec163811d078815c633f21901413081449002f24aa3e80f0b88ef7"); + CHECK_HASH(56, 0, "d4817aa5497628e7c77e6b606107042bbba3130888c5f47a375e6179be789fbb"); + CHECK_HASH(57, 0, "65a16cb7861335d5ace3c60718b5052e44660726da4cd13bb745381b235a1785"); + CHECK_HASH(64, 0, "f5a5fd42d16a20302798ef6ed309979b43003d2320d9f0e8ea9831a92759fb4b"); + CHECK_HASH(1000, 0, "541b3e9daa09b20bf85fa273e5cbd3e80185aa4ec298e765db87742b70138a53"); + CHECK_HASH(1000, 'A', "c2e686823489ced2017f6059b8b239318b6364f6dcd835d0a519105a1eadd6e4"); + CHECK_HASH(1005, 'U', "f4d62ddec0f3dd90ea1380fa16a5ff8dc4c54b21740650f24afc4120903552b0"); +} + +TEST_CASE ("SHA512: NIST test cases (small)", "[hash][sha512]") +{ + const auto algorithm = Hash::Algorithm::Sha512; + + CHECK_HASH_STRING("", + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f" + "63b931bd47417a81a538327af927da3e"); + + CHECK_HASH(111, + 0, + "77ddd3a542e530fd047b8977c657ba6ce72f1492e360b2b2212cd264e75ec03882e4ff0525517ab4207d14c70c2259ba88d4d33" + "5ee0e7e20543d22102ab1788c"); + CHECK_HASH(112, + 0, + "2be2e788c8a8adeaa9c89a7f78904cacea6e39297d75e0573a73c756234534d6627ab4156b48a6657b29ab8beb73334040ad39e" + "ad81446bb09c70704ec707952"); + CHECK_HASH(113, + 0, + "0e67910bcf0f9ccde5464c63b9c850a12a759227d16b040d98986d54253f9f34322318e56b8feb86c5fb2270ed87f31252f7f68" + "493ee759743909bd75e4bb544"); + CHECK_HASH(122, + 0, + "4f3f095d015be4a7a7cc0b8c04da4aa09e74351e3a97651f744c23716ebd9b3e822e5077a01baa5cc0ed45b9249e88ab343d433" + "3539df21ed229da6f4a514e0f"); + CHECK_HASH(1000, + 0, + "ca3dff61bb23477aa6087b27508264a6f9126ee3a004f53cb8db942ed345f2f2d229b4b59c859220a1cf1913f34248e3803bab6" + "50e849a3d9a709edc09ae4a76"); + CHECK_HASH(1000, + 'A', + "329c52ac62d1fe731151f2b895a00475445ef74f50b979c6f7bb7cae349328c1d4cb4f7261a0ab43f936a24b000651d4a824fcd" + "d577f211aef8f806b16afe8af"); + CHECK_HASH(1005, + 'U', + "59f5e54fe299c6a8764c6b199e44924a37f59e2b56c3ebad939b7289210dc8e4c21b9720165b0f4d4374c90f1bf4fb4a5ace17a" + "1161798015052893a48c3d161"); +} + +TEST_CASE ("SHA256: NIST test cases (large)", "[.][hash-expensive][sha256-expensive]") +{ + auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256); + CHECK_HASH_LARGE(1'000'000, 0, "d29751f2649b32ff572b5e0a9f541ea660a50f94ff0beedfb0b692b924cc8025"); + CHECK_HASH_LARGE(0x2000'0000, 'Z', "15a1868c12cc53951e182344277447cd0979536badcc512ad24c67e9b2d4f3dd"); + CHECK_HASH_LARGE(0x4100'0000, 0, "461c19a93bd4344f9215f5ec64357090342bc66b15a148317d276e31cbc20b53"); + CHECK_HASH_LARGE(0x6000'003E, 'B', "c23ce8a7895f4b21ec0daf37920ac0a262a220045a03eb2dfed48ef9b05aabea"); +} + +TEST_CASE ("SHA512: NIST test cases (large)", "[.][hash-expensive][sha512-expensive]") +{ + auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha512); + CHECK_HASH_LARGE(1'000'000, + 0, + "ce044bc9fd43269d5bbc946cbebc3bb711341115cc4abdf2edbc3ff2c57ad4b15deb699bda257fea5aef9c6e55fcf4cf9" + "dc25a8c3ce25f2efe90908379bff7ed"); + CHECK_HASH_LARGE(0x2000'0000, + 'Z', + "da172279f3ebbda95f6b6e1e5f0ebec682c25d3d93561a1624c2fa9009d64c7e9923f3b46bcaf11d39a531f43297992ba" + "4155c7e827bd0f1e194ae7ed6de4cac"); + CHECK_HASH_LARGE(0x4100'0000, + 0, + "14b1be901cb43549b4d831e61e5f9df1c791c85b50e85f9d6bc64135804ad43ce8402750edbe4e5c0fc170b99cf78b9f4" + "ecb9c7e02a157911d1bd1832d76784f"); + CHECK_HASH_LARGE(0x6000'003E, + 'B', + "fd05e13eb771f05190bd97d62647157ea8f1f6949a52bb6daaedbad5f578ec59b1b8d6c4a7ecb2feca6892b4dc1387716" + "70a0f3bd577eea326aed40ab7dd58b1"); +} + +#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING) +using Catch::Benchmark::Chronometer; +void benchmark_hasher(Chronometer& meter, Hash::Hasher& hasher, std::uint64_t size, unsigned char byte) noexcept +{ + unsigned char buffer[1024]; + std::fill(std::begin(buffer), std::end(buffer), byte); + + meter.measure([&] { + hasher.clear(); + std::uint64_t remaining = size; + while (remaining) + { + if (remaining < 512) + { + hasher.add_bytes(std::begin(buffer), std::begin(buffer) + remaining); + remaining = 0; + } + else + { + hasher.add_bytes(std::begin(buffer), std::end(buffer)); + remaining -= 512; + } + } + hasher.get_hash(); + }); +} + +TEST_CASE ("SHA1: benchmark", "[.][hash][sha256][!benchmark]") +{ + using Catch::Benchmark::Chronometer; + + auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha1); + + BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 1'000'000, 0); + }; + BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z'); + }; + BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x4100'0000, 0); + }; + BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x6000'003E, 'B'); + }; +} + +TEST_CASE ("SHA256: benchmark", "[.][hash][sha256][!benchmark]") +{ + using Catch::Benchmark::Chronometer; + + auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha256); + + BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 1'000'000, 0); + }; + BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z'); + }; + BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x4100'0000, 0); + }; + BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x6000'003E, 'B'); + }; +} + +TEST_CASE ("SHA512: large -- benchmark", "[.][hash][sha512][!benchmark]") +{ + auto hasher = Hash::get_hasher_for(Hash::Algorithm::Sha512); + + BENCHMARK_ADVANCED("0 x 1'000'000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 1'000'000, 0); + }; + BENCHMARK_ADVANCED("'Z' x 0x2000'0000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x2000'0000, 'Z'); + }; + BENCHMARK_ADVANCED("0 x 0x4100'0000")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x4100'0000, 0); + }; + BENCHMARK_ADVANCED("'B' x 0x6000'003E")(Catch::Benchmark::Chronometer meter) + { + benchmark_hasher(meter, *hasher, 0x6000'003E, 'B'); + }; +} +#endif diff --git a/toolsrc/src/vcpkg-test/paragraph.cpp b/toolsrc/src/vcpkg-test/paragraph.cpp new file mode 100644 index 000000000..85c37851d --- /dev/null +++ b/toolsrc/src/vcpkg-test/paragraph.cpp @@ -0,0 +1,445 @@ +#include <catch2/catch.hpp> +#include <vcpkg-test/util.h> + +#include <vcpkg/base/strings.h> + +#include <vcpkg/paragraphs.h> + +namespace Strings = vcpkg::Strings; + +TEST_CASE ("SourceParagraph construct minimum", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + }}); + + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->name == "zlib"); + REQUIRE(pgh.core_paragraph->version == "1.2.8"); + REQUIRE(pgh.core_paragraph->maintainer == ""); + REQUIRE(pgh.core_paragraph->description == ""); + REQUIRE(pgh.core_paragraph->depends.size() == 0); +} + +TEST_CASE ("SourceParagraph construct maximum", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "s"}, + {"Version", "v"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Build-Depends", "bd"}, + {"Default-Features", "df"}, + {"Supports", "x64"}, + }}); + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->name == "s"); + REQUIRE(pgh.core_paragraph->version == "v"); + REQUIRE(pgh.core_paragraph->maintainer == "m"); + REQUIRE(pgh.core_paragraph->description == "d"); + REQUIRE(pgh.core_paragraph->depends.size() == 1); + REQUIRE(pgh.core_paragraph->depends[0].name() == "bd"); + REQUIRE(pgh.core_paragraph->default_features.size() == 1); + REQUIRE(pgh.core_paragraph->default_features[0] == "df"); + REQUIRE(pgh.core_paragraph->supports.size() == 1); + REQUIRE(pgh.core_paragraph->supports[0] == "x64"); +} + +TEST_CASE ("SourceParagraph two depends", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl"}, + }}); + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->depends.size() == 2); + REQUIRE(pgh.core_paragraph->depends[0].name() == "z"); + REQUIRE(pgh.core_paragraph->depends[1].name() == "openssl"); +} + +TEST_CASE ("SourceParagraph three depends", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl, xyz"}, + }}); + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->depends.size() == 3); + REQUIRE(pgh.core_paragraph->depends[0].name() == "z"); + REQUIRE(pgh.core_paragraph->depends[1].name() == "openssl"); + REQUIRE(pgh.core_paragraph->depends[2].name() == "xyz"); +} + +TEST_CASE ("SourceParagraph three supports", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Supports", "x64, windows, uwp"}, + }}); + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->supports.size() == 3); + REQUIRE(pgh.core_paragraph->supports[0] == "x64"); + REQUIRE(pgh.core_paragraph->supports[1] == "windows"); + REQUIRE(pgh.core_paragraph->supports[2] == "uwp"); +} + +TEST_CASE ("SourceParagraph construct qualified depends", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "libA (windows), libB (uwp)"}, + }}); + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->name == "zlib"); + REQUIRE(pgh.core_paragraph->version == "1.2.8"); + REQUIRE(pgh.core_paragraph->maintainer == ""); + REQUIRE(pgh.core_paragraph->description == ""); + REQUIRE(pgh.core_paragraph->depends.size() == 2); + REQUIRE(pgh.core_paragraph->depends[0].name() == "libA"); + REQUIRE(pgh.core_paragraph->depends[0].qualifier == "windows"); + REQUIRE(pgh.core_paragraph->depends[1].name() == "libB"); + REQUIRE(pgh.core_paragraph->depends[1].qualifier == "uwp"); +} + +TEST_CASE ("SourceParagraph default features", "[paragraph]") +{ + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "a"}, + {"Version", "1.0"}, + {"Default-Features", "a1"}, + }}); + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + + REQUIRE(pgh.core_paragraph->default_features.size() == 1); + REQUIRE(pgh.core_paragraph->default_features[0] == "a1"); +} + +TEST_CASE ("BinaryParagraph construct minimum", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + }); + + REQUIRE(pgh.spec.name() == "zlib"); + REQUIRE(pgh.version == "1.2.8"); + REQUIRE(pgh.maintainer == ""); + REQUIRE(pgh.description == ""); + REQUIRE(pgh.spec.triplet().canonical_name() == "x86-windows"); + REQUIRE(pgh.depends.size() == 0); +} + +TEST_CASE ("BinaryParagraph construct maximum", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "s"}, + {"Version", "v"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Depends", "bd"}, + }); + + REQUIRE(pgh.spec.name() == "s"); + REQUIRE(pgh.version == "v"); + REQUIRE(pgh.maintainer == "m"); + REQUIRE(pgh.description == "d"); + REQUIRE(pgh.depends.size() == 1); + REQUIRE(pgh.depends[0] == "bd"); +} + +TEST_CASE ("BinaryParagraph three depends", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "a, b, c"}, + }); + + REQUIRE(pgh.depends.size() == 3); + REQUIRE(pgh.depends[0] == "a"); + REQUIRE(pgh.depends[1] == "b"); + REQUIRE(pgh.depends[2] == "c"); +} + +TEST_CASE ("BinaryParagraph abi", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Abi", "abcd123"}, + }); + + REQUIRE(pgh.depends.size() == 0); + REQUIRE(pgh.abi == "abcd123"); +} + +TEST_CASE ("BinaryParagraph default features", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "a"}, + {"Version", "1.0"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Default-Features", "a1"}, + }); + + REQUIRE(pgh.depends.size() == 0); + REQUIRE(pgh.default_features.size() == 1); + REQUIRE(pgh.default_features[0] == "a1"); +} + +TEST_CASE ("parse paragraphs empty", "[paragraph]") +{ + const char* str = ""; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + REQUIRE(pghs.empty()); +} + +TEST_CASE ("parse paragraphs one field", "[paragraph]") +{ + const char* str = "f1: v1"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0].size() == 1); + REQUIRE(pghs[0]["f1"] == "v1"); +} + +TEST_CASE ("parse paragraphs one pgh", "[paragraph]") +{ + const char* str = "f1: v1\n" + "f2: v2"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0].size() == 2); + REQUIRE(pghs[0]["f1"] == "v1"); + REQUIRE(pghs[0]["f2"] == "v2"); +} + +TEST_CASE ("parse paragraphs two pgh", "[paragraph]") +{ + const char* str = "f1: v1\n" + "f2: v2\n" + "\n" + "f3: v3\n" + "f4: v4"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 2); + REQUIRE(pghs[0].size() == 2); + REQUIRE(pghs[0]["f1"] == "v1"); + REQUIRE(pghs[0]["f2"] == "v2"); + REQUIRE(pghs[1].size() == 2); + REQUIRE(pghs[1]["f3"] == "v3"); + REQUIRE(pghs[1]["f4"] == "v4"); +} + +TEST_CASE ("parse paragraphs field names", "[paragraph]") +{ + const char* str = "1:\n" + "f:\n" + "F:\n" + "0:\n" + "F-2:\n"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0].size() == 5); +} + +TEST_CASE ("parse paragraphs multiple blank lines", "[paragraph]") +{ + const char* str = "f1: v1\n" + "f2: v2\n" + "\n" + "\n" + "f3: v3\n" + "f4: v4"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 2); +} + +TEST_CASE ("parse paragraphs empty fields", "[paragraph]") +{ + const char* str = "f1:\n" + "f2: "; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0].size() == 2); + REQUIRE(pghs[0]["f1"] == ""); + REQUIRE(pghs[0]["f2"] == ""); + REQUIRE(pghs[0].size() == 2); +} + +TEST_CASE ("parse paragraphs multiline fields", "[paragraph]") +{ + const char* str = "f1: simple\n" + " f1\r\n" + "f2:\r\n" + " f2\r\n" + " continue\r\n"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0]["f1"] == "simple\n f1"); + REQUIRE(pghs[0]["f2"] == "\n f2\n continue"); +} + +TEST_CASE ("parse paragraphs crlfs", "[paragraph]") +{ + const char* str = "f1: v1\r\n" + "f2: v2\r\n" + "\r\n" + "f3: v3\r\n" + "f4: v4"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 2); + REQUIRE(pghs[0].size() == 2); + REQUIRE(pghs[0]["f1"] == "v1"); + REQUIRE(pghs[0]["f2"] == "v2"); + REQUIRE(pghs[1].size() == 2); + REQUIRE(pghs[1]["f3"] == "v3"); + REQUIRE(pghs[1]["f4"] == "v4"); +} + +TEST_CASE ("parse paragraphs comment", "[paragraph]") +{ + const char* str = "f1: v1\r\n" + "#comment\r\n" + "f2: v2\r\n" + "#comment\r\n" + "\r\n" + "#comment\r\n" + "f3: v3\r\n" + "#comment\r\n" + "f4: v4"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 2); + REQUIRE(pghs[0].size() == 2); + REQUIRE(pghs[0]["f1"] == "v1"); + REQUIRE(pghs[0]["f2"] == "v2"); + REQUIRE(pghs[1].size()); + REQUIRE(pghs[1]["f3"] == "v3"); + REQUIRE(pghs[1]["f4"] == "v4"); +} + +TEST_CASE ("parse comment before single line feed", "[paragraph]") +{ + const char* str = "f1: v1\r\n" + "#comment\n"; + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str).value_or_exit(VCPKG_LINE_INFO); + REQUIRE(pghs[0].size() == 1); + REQUIRE(pghs[0]["f1"] == "v1"); +} + +TEST_CASE ("BinaryParagraph serialize min", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + }); + std::string ss = Strings::serialize(pgh); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0].size() == 4); + REQUIRE(pghs[0]["Package"] == "zlib"); + REQUIRE(pghs[0]["Version"] == "1.2.8"); + REQUIRE(pghs[0]["Architecture"] == "x86-windows"); + REQUIRE(pghs[0]["Multi-Arch"] == "same"); +} + +TEST_CASE ("BinaryParagraph serialize max", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Description", "first line\n second line"}, + {"Maintainer", "abc <abc@abc.abc>"}, + {"Depends", "dep"}, + {"Multi-Arch", "same"}, + }); + std::string ss = Strings::serialize(pgh); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0].size() == 7); + REQUIRE(pghs[0]["Package"] == "zlib"); + REQUIRE(pghs[0]["Version"] == "1.2.8"); + REQUIRE(pghs[0]["Architecture"] == "x86-windows"); + REQUIRE(pghs[0]["Multi-Arch"] == "same"); + REQUIRE(pghs[0]["Description"] == "first line\n second line"); + REQUIRE(pghs[0]["Depends"] == "dep"); +} + +TEST_CASE ("BinaryParagraph serialize multiple deps", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "a, b, c"}, + }); + std::string ss = Strings::serialize(pgh); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0]["Depends"] == "a, b, c"); +} + +TEST_CASE ("BinaryParagraph serialize abi", "[paragraph]") +{ + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "a, b, c"}, + {"Abi", "123abc"}, + }); + std::string ss = Strings::serialize(pgh); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); + + REQUIRE(pghs.size() == 1); + REQUIRE(pghs[0]["Abi"] == "123abc"); +} diff --git a/toolsrc/src/vcpkg-test/plan.cpp b/toolsrc/src/vcpkg-test/plan.cpp new file mode 100644 index 000000000..e354b7551 --- /dev/null +++ b/toolsrc/src/vcpkg-test/plan.cpp @@ -0,0 +1,1241 @@ +#include <catch2/catch.hpp> +#include <vcpkg-test/util.h> + +#include <vcpkg/dependencies.h> +#include <vcpkg/sourceparagraph.h> +#include <vcpkg/triplet.h> + +#include <memory> +#include <unordered_map> +#include <vector> + +using namespace vcpkg; + +using Test::make_status_feature_pgh; +using Test::make_status_pgh; +using Test::unsafe_pspec; + +static std::unique_ptr<SourceControlFile> make_control_file( + const char* name, + const char* depends, + const std::vector<std::pair<const char*, const char*>>& features = {}, + const std::vector<const char*>& default_features = {}) +{ + using Pgh = std::unordered_map<std::string, std::string>; + std::vector<Pgh> scf_pghs; + scf_pghs.push_back(Pgh{{"Source", name}, + {"Version", "0"}, + {"Build-Depends", depends}, + {"Default-Features", Strings::join(", ", default_features)}}); + for (auto&& feature : features) + { + scf_pghs.push_back(Pgh{ + {"Feature", feature.first}, + {"Description", "feature"}, + {"Build-Depends", feature.second}, + }); + } + auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(scf_pghs)); + REQUIRE(m_pgh.has_value()); + return std::move(*m_pgh.get()); +} + +/// <summary> +/// Assert that the given action an install of given features from given package. +/// </summary> +static void features_check(Dependencies::AnyAction& install_action, + std::string pkg_name, + std::vector<std::string> vec, + const Triplet& triplet = Triplet::X86_WINDOWS) +{ + REQUIRE(install_action.install_action.has_value()); + const auto& plan = install_action.install_action.value_or_exit(VCPKG_LINE_INFO); + const auto& feature_list = plan.feature_list; + + REQUIRE(plan.spec.triplet().to_string() == triplet.to_string()); + + auto& scfl = *plan.source_control_file_location.get(); + REQUIRE(pkg_name == scfl.source_control_file->core_paragraph->name); + REQUIRE(feature_list.size() == vec.size()); + + for (auto&& feature_name : vec) + { + // TODO: see if this can be simplified + if (feature_name == "core" || feature_name == "") + { + REQUIRE((Util::find(feature_list, "core") != feature_list.end() || + Util::find(feature_list, "") != feature_list.end())); + continue; + } + REQUIRE(Util::find(feature_list, feature_name) != feature_list.end()); + } +} + +/// <summary> +/// Assert that the given action is a remove of given package. +/// </summary> +static void remove_plan_check(Dependencies::AnyAction& remove_action, + std::string pkg_name, + const Triplet& triplet = Triplet::X86_WINDOWS) +{ + const auto& plan = remove_action.remove_action.value_or_exit(VCPKG_LINE_INFO); + REQUIRE(plan.spec.triplet().to_string() == triplet.to_string()); + REQUIRE(pkg_name == plan.spec.name()); +} + +/// <summary> +/// Map of source control files by their package name. +/// </summary> +struct PackageSpecMap +{ + std::unordered_map<std::string, SourceControlFileLocation> map; + Triplet triplet; + PackageSpecMap(const Triplet& t = Triplet::X86_WINDOWS) noexcept { triplet = t; } + + PackageSpec emplace(const char* name, + const char* depends = "", + const std::vector<std::pair<const char*, const char*>>& features = {}, + const std::vector<const char*>& default_features = {}) + { + auto scfl = SourceControlFileLocation{make_control_file(name, depends, features, default_features), ""}; + return emplace(std::move(scfl)); + } + + PackageSpec emplace(vcpkg::SourceControlFileLocation&& scfl) + { + auto spec = PackageSpec::from_name_and_triplet(scfl.source_control_file->core_paragraph->name, triplet); + REQUIRE(spec.has_value()); + map.emplace(scfl.source_control_file->core_paragraph->name, std::move(scfl)); + return PackageSpec{*spec.get()}; + } +}; + +TEST_CASE ("basic install scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + 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"); + + Dependencies::MapPortFileProvider map_port(spec_map.map); + auto install_plan = Dependencies::create_feature_install_plan( + map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 3); + REQUIRE(install_plan.at(0).spec().name() == "c"); + REQUIRE(install_plan.at(1).spec().name() == "b"); + REQUIRE(install_plan.at(2).spec().name() == "a"); +} + +TEST_CASE ("multiple install scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + 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"); + auto spec_d = spec_map.emplace("d", "f, g, h"); + auto spec_e = spec_map.emplace("e", "g"); + auto spec_f = spec_map.emplace("f"); + auto spec_g = spec_map.emplace("g"); + auto spec_h = spec_map.emplace("h"); + + Dependencies::MapPortFileProvider map_port(spec_map.map); + auto install_plan = Dependencies::create_feature_install_plan( + map_port, + {FeatureSpec{spec_a, ""}, FeatureSpec{spec_b, ""}, FeatureSpec{spec_c, ""}}, + StatusParagraphs(std::move(status_paragraphs))); + + auto iterator_pos = [&](const PackageSpec& spec) { + auto it = + std::find_if(install_plan.begin(), install_plan.end(), [&](auto& action) { return action.spec() == spec; }); + REQUIRE(it != install_plan.end()); + return it - install_plan.begin(); + }; + + const auto a_pos = iterator_pos(spec_a); + const auto b_pos = iterator_pos(spec_b); + const auto c_pos = iterator_pos(spec_c); + const auto d_pos = iterator_pos(spec_d); + const auto e_pos = iterator_pos(spec_e); + const auto f_pos = iterator_pos(spec_f); + const auto g_pos = iterator_pos(spec_g); + const auto h_pos = iterator_pos(spec_h); + + REQUIRE(a_pos > d_pos); + REQUIRE(b_pos > e_pos); + REQUIRE(b_pos > d_pos); + REQUIRE(c_pos > e_pos); + REQUIRE(c_pos > h_pos); + REQUIRE(d_pos > f_pos); + REQUIRE(d_pos > g_pos); + REQUIRE(d_pos > h_pos); + REQUIRE(e_pos > g_pos); +} + +TEST_CASE ("existing package scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(vcpkg::Test::make_status_pgh("a")); + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a")}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 1); + const auto p = install_plan.at(0).install_action.get(); + REQUIRE(p); + REQUIRE(p->spec.name() == "a"); + REQUIRE(p->plan_type == Dependencies::InstallPlanType::ALREADY_INSTALLED); + REQUIRE(p->request_type == Dependencies::RequestType::USER_REQUESTED); +} + +TEST_CASE ("user requested package scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + const auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")}; + const auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + + const auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 2); + const auto p = install_plan.at(0).install_action.get(); + REQUIRE(p); + REQUIRE(p->spec.name() == "b"); + REQUIRE(p->plan_type == Dependencies::InstallPlanType::BUILD_AND_INSTALL); + REQUIRE(p->request_type == Dependencies::RequestType::AUTO_SELECTED); + + const auto p2 = install_plan.at(1).install_action.get(); + REQUIRE(p2); + REQUIRE(p2->spec.name() == "a"); + REQUIRE(p2->plan_type == Dependencies::InstallPlanType::BUILD_AND_INSTALL); + REQUIRE(p2->request_type == Dependencies::RequestType::USER_REQUESTED); +} + +TEST_CASE ("long install scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("j", "k")); + status_paragraphs.push_back(make_status_pgh("k")); + + 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"); + auto spec_c = spec_map.emplace("c", "d, e, f, g, h, j, k"); + auto spec_d = spec_map.emplace("d", "e, f, g, h, j, k"); + auto spec_e = spec_map.emplace("e", "f, g, h, j, k"); + auto spec_f = spec_map.emplace("f", "g, h, j, k"); + auto spec_g = spec_map.emplace("g", "h, j, k"); + auto spec_h = spec_map.emplace("h", "j, k"); + auto spec_j = spec_map.emplace("j", "k"); + auto spec_k = spec_map.emplace("k"); + + Dependencies::MapPortFileProvider map_port(spec_map.map); + auto install_plan = Dependencies::create_feature_install_plan( + map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 8); + REQUIRE(install_plan.at(0).spec().name() == "h"); + REQUIRE(install_plan.at(1).spec().name() == "g"); + REQUIRE(install_plan.at(2).spec().name() == "f"); + REQUIRE(install_plan.at(3).spec().name() == "e"); + REQUIRE(install_plan.at(4).spec().name() == "d"); + REQUIRE(install_plan.at(5).spec().name() == "c"); + REQUIRE(install_plan.at(6).spec().name() == "b"); + REQUIRE(install_plan.at(7).spec().name() == "a"); +} + +TEST_CASE ("basic feature test 1", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a", "b, b[b1]")); + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.push_back(make_status_feature_pgh("b", "b1")); + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1]", {{"a1", "b[b2]"}}), {"a1"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 4); + remove_plan_check(install_plan.at(0), "a"); + remove_plan_check(install_plan.at(1), "b"); + features_check(install_plan.at(2), "b", {"b1", "core", "b1"}); + features_check(install_plan.at(3), "a", {"a1", "core"}); +} + +TEST_CASE ("basic feature test 2", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + 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", ""}})}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 2); + features_check(install_plan.at(0), "b", {"b1", "b2", "core"}); + features_check(install_plan.at(1), "a", {"a1", "core"}); +} + +TEST_CASE ("basic feature test 3", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + auto install_plan = Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_c, spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 4); + remove_plan_check(install_plan.at(0), "a"); + features_check(install_plan.at(1), "b", {"core"}); + features_check(install_plan.at(2), "a", {"a1", "core"}); + features_check(install_plan.at(3), "c", {"core"}); +} + +TEST_CASE ("basic feature test 4", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.push_back(make_status_feature_pgh("a", "a1", "")); + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}})}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_c}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "c", {"core"}); +} + +TEST_CASE ("basic feature test 5", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + + auto spec_a = + FullPackageSpec{spec_map.emplace("a", "", {{"a1", "b[b1]"}, {"a2", "b[b2]"}, {"a3", "a[a2]"}}), {"a3"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}})}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_a}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 2); + features_check(install_plan.at(0), "b", {"core", "b2"}); + features_check(install_plan.at(1), "a", {"core", "a3", "a2"}); +} + +TEST_CASE ("basic feature test 6", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("b")); + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[core]"), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; + + auto install_plan = Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a, spec_b}), + StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 3); + remove_plan_check(install_plan.at(0), "b"); + features_check(install_plan.at(1), "b", {"core", "b1"}); + features_check(install_plan.at(2), "a", {"core"}); +} + +TEST_CASE ("basic feature test 7", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("x", "b")); + status_paragraphs.push_back(make_status_pgh("b")); + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a")}; + auto spec_x = FullPackageSpec{spec_map.emplace("x", "a"), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, FullPackageSpec::to_feature_specs({spec_b}), StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 5); + remove_plan_check(install_plan.at(0), "x"); + remove_plan_check(install_plan.at(1), "b"); + + // TODO: order here may change but A < X, and B anywhere + features_check(install_plan.at(2), "b", {"core", "b1"}); + features_check(install_plan.at(3), "a", {"core"}); + features_check(install_plan.at(4), "x", {"core"}); +} + +TEST_CASE ("basic feature test 8", "[plan][!mayfail]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + spec_map.triplet = Triplet::X86_WINDOWS; + auto spec_a_86 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b_86 = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c_86 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({spec_c_64, spec_a_86, spec_a_64, spec_c_86}), + StatusParagraphs(std::move(status_paragraphs))); + + remove_plan_check(install_plan.at(0), "a", Triplet::X64_WINDOWS); + remove_plan_check(install_plan.at(1), "a"); + features_check(install_plan.at(2), "b", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(3), "a", {"a1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(4), "c", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(5), "b", {"core"}); + features_check(install_plan.at(6), "a", {"a1", "core"}); + features_check(install_plan.at(7), "c", {"core"}); +} + +TEST_CASE ("install all features test", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + REQUIRE(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "a", {"0", "1", "core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install default features test 1", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" with default features "1" and features "0" and "1". + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"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))); + + // Expect the default feature "1" to be installed, but not "0" + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "a", {"1", "core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install default features test 2", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // Add a port "a" of which "core" is already installed, but we will + // install the default features "explicitly" + // "a" has two features, of which "a1" is default. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); + + // 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))); + + // Expect "a" to get removed for rebuild and then installed with default + // features. + REQUIRE(install_plan.size() == 2); + remove_plan_check(install_plan.at(0), "a", Triplet::X64_WINDOWS); + features_check(install_plan.at(1), "a", {"a1", "core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install default features test 3", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // "a" has two features, of which "a1" is default. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); + + // Explicitly install "a" without default features + auto install_specs = FullPackageSpec::from_string("a[core]", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect the default feature not to get installed. + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install default features of dependency test 1", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto 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))); + + // Expect "a" to get installed and defaults of "b" through the dependency, + // as no explicit features of "b" are installed by the user. + REQUIRE(install_plan.size() == 2); + features_check(install_plan.at(0), "b", {"b1", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(1), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("do not install default features of existing dependency", "[plan]") +{ + // Add a port "a" which depends on the core of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + // "b[core]" is already installed + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // 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))); + + // Expect "a" to get installed, but not require rebuilding "b" + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install default features of dependency test 2", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // 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", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto 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))); + + // Expect "a" to get installed, not the defaults of "b", as the required + // dependencies are already there, installed explicitly by the user. + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("install plan action dependencies", "[plan]") +{ + 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))); + + REQUIRE(install_plan.size() == 3); + features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS); + + features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS); + REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c}); + + features_check(install_plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS); + REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b}); +} + +TEST_CASE ("install plan action dependencies 2", "[plan]") +{ + 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))); + + REQUIRE(install_plan.size() == 3); + features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS); + + features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS); + REQUIRE(install_plan.at(1).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_c}); + + features_check(install_plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS); + REQUIRE(install_plan.at(2).install_action.get()->computed_dependencies == std::vector<PackageSpec>{spec_b, spec_c}); +} + +TEST_CASE ("install plan action dependencies 3", "[plan]") +{ + 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))); + + REQUIRE(install_plan.size() == 1); + features_check(install_plan.at(0), "a", {"1", "0", "core"}, Triplet::X64_WINDOWS); + REQUIRE(install_plan.at(0).install_action.get()->computed_dependencies == std::vector<PackageSpec>{}); +} + +TEST_CASE ("install with default features", "[plan]") +{ + 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); + + REQUIRE(install_plan.size() == 3); + remove_plan_check(install_plan.at(0), "a"); + features_check(install_plan.at(1), "b", {"core"}); + features_check(install_plan.at(2), "a", {"0", "core"}); +} + +TEST_CASE ("upgrade with default features 1", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "", "1")); + pghs.push_back(make_status_feature_pgh("a", "0")); + StatusParagraphs status_db(std::move(pghs)); + + // Add a port "a" of which "core" and "0" are already installed. + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should not install the default feature + REQUIRE(plan.size() == 2); + + REQUIRE(plan.at(0).spec().name() == "a"); + remove_plan_check(plan.at(0), "a"); + features_check(plan.at(1), "a", {"core", "0"}); +} + +TEST_CASE ("upgrade with default features 2", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + // 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 + REQUIRE(plan.size() == 4); + remove_plan_check(plan.at(0), "a", Triplet::X64_WINDOWS); + remove_plan_check(plan.at(1), "b", Triplet::X64_WINDOWS); + features_check(plan.at(2), "b", {"core", "b1"}, Triplet::X64_WINDOWS); + features_check(plan.at(3), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("upgrade with default features 3", "[plan]") +{ + 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)); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a = spec_map.emplace("a", "b[core]"); + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should install the default feature + REQUIRE(plan.size() == 3); + remove_plan_check(plan.at(0), "a", Triplet::X64_WINDOWS); + features_check(plan.at(1), "b", {"b0", "core"}, Triplet::X64_WINDOWS); + features_check(plan.at(2), "a", {"core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("upgrade with new default feature", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "", "0", "x86-windows")); + + StatusParagraphs status_db(std::move(pghs)); + + 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 + REQUIRE(plan.size() == 2); + remove_plan_check(plan.at(0), "a", Triplet::X86_WINDOWS); + features_check(plan.at(1), "a", {"core", "1"}, Triplet::X86_WINDOWS); +} + +TEST_CASE ("transitive features test", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", "b[0]"}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", "c[0]"}}), {"core"}}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + REQUIRE(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 3); + features_check(install_plan.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("no transitive features test", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", ""}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", ""}}), {"core"}}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + REQUIRE(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 3); + features_check(install_plan.at(0), "c", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(1), "b", {"core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("only transitive features test", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", "b[0]"}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "", {{"0", "c[0]"}}), {"core"}}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + REQUIRE(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + REQUIRE(install_plan.size() == 3); + features_check(install_plan.at(0), "c", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(1), "b", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(install_plan.at(2), "a", {"0", "core"}, Triplet::X64_WINDOWS); +} + +TEST_CASE ("basic remove scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); + + REQUIRE(remove_plan.size() == 1); + REQUIRE(remove_plan.at(0).spec.name() == "a"); +} + +TEST_CASE ("recurse remove scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b", "a")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); + + REQUIRE(remove_plan.size() == 2); + REQUIRE(remove_plan.at(0).spec.name() == "b"); + REQUIRE(remove_plan.at(1).spec.name() == "a"); +} + +TEST_CASE ("features depend remove scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b")); + pghs.push_back(make_status_feature_pgh("b", "0", "a")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); + + REQUIRE(remove_plan.size() == 2); + REQUIRE(remove_plan.at(0).spec.name() == "b"); + REQUIRE(remove_plan.at(1).spec.name() == "a"); +} + +TEST_CASE ("features depend remove scheme once removed", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("expat")); + pghs.push_back(make_status_pgh("vtk", "expat")); + pghs.push_back(make_status_pgh("opencv")); + pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("expat")}, status_db); + + REQUIRE(remove_plan.size() == 3); + REQUIRE(remove_plan.at(0).spec.name() == "opencv"); + REQUIRE(remove_plan.at(1).spec.name() == "vtk"); + REQUIRE(remove_plan.at(2).spec.name() == "expat"); +} + +TEST_CASE ("features depend remove scheme once removed x64", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("expat", "", "", "x64")); + pghs.push_back(make_status_pgh("vtk", "expat", "", "x64")); + pghs.push_back(make_status_pgh("opencv", "", "", "x64")); + pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk", "x64")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = + Dependencies::create_remove_plan({unsafe_pspec("expat", Triplet::from_canonical_name("x64"))}, status_db); + + REQUIRE(remove_plan.size() == 3); + REQUIRE(remove_plan.at(0).spec.name() == "opencv"); + REQUIRE(remove_plan.at(1).spec.name() == "vtk"); + REQUIRE(remove_plan.at(2).spec.name() == "expat"); +} + +TEST_CASE ("features depend core remove scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("curl", "", "", "x64")); + pghs.push_back(make_status_pgh("cpr", "curl[core]", "", "x64")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = + Dependencies::create_remove_plan({unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db); + + REQUIRE(remove_plan.size() == 2); + REQUIRE(remove_plan.at(0).spec.name() == "cpr"); + REQUIRE(remove_plan.at(1).spec.name() == "curl"); +} + +TEST_CASE ("features depend core remove scheme 2", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("curl", "", "", "x64")); + pghs.push_back(make_status_feature_pgh("curl", "a", "", "x64")); + pghs.push_back(make_status_feature_pgh("curl", "b", "curl[a]", "x64")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = + Dependencies::create_remove_plan({unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db); + + REQUIRE(remove_plan.size() == 1); + REQUIRE(remove_plan.at(0).spec.name() == "curl"); +} + +TEST_CASE ("basic upgrade scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 2); + REQUIRE(plan.at(0).spec().name() == "a"); + REQUIRE(plan.at(0).remove_action.has_value()); + REQUIRE(plan.at(1).spec().name() == "a"); + REQUIRE(plan.at(1).install_action.has_value()); +} + +TEST_CASE ("basic upgrade scheme with recurse", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b", "a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + spec_map.emplace("b", "a"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 4); + REQUIRE(plan.at(0).spec().name() == "b"); + REQUIRE(plan.at(0).remove_action.has_value()); + + REQUIRE(plan.at(1).spec().name() == "a"); + REQUIRE(plan.at(1).remove_action.has_value()); + + REQUIRE(plan.at(2).spec().name() == "a"); + REQUIRE(plan.at(2).install_action.has_value()); + + REQUIRE(plan.at(3).spec().name() == "b"); + REQUIRE(plan.at(3).install_action.has_value()); +} + +TEST_CASE ("basic upgrade scheme with bystander", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + spec_map.emplace("b", "a"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 2); + REQUIRE(plan.at(0).spec().name() == "a"); + REQUIRE(plan.at(0).remove_action.has_value()); + REQUIRE(plan.at(1).spec().name() == "a"); + REQUIRE(plan.at(1).install_action.has_value()); +} + +TEST_CASE ("basic upgrade scheme with new dep", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "b"); + spec_map.emplace("b"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 3); + REQUIRE(plan.at(0).spec().name() == "a"); + REQUIRE(plan.at(0).remove_action.has_value()); + REQUIRE(plan.at(1).spec().name() == "b"); + REQUIRE(plan.at(1).install_action.has_value()); + REQUIRE(plan.at(2).spec().name() == "a"); + REQUIRE(plan.at(2).install_action.has_value()); +} + +TEST_CASE ("basic upgrade scheme with features", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_feature_pgh("a", "a1")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 2); + + REQUIRE(plan.at(0).spec().name() == "a"); + REQUIRE(plan.at(0).remove_action.has_value()); + + features_check(plan.at(1), "a", {"core", "a1"}); +} + +TEST_CASE ("basic upgrade scheme with new default feature", "[plan]") +{ + // only core of package "a" is installed + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + // a1 was added as a default feature and should be installed in upgrade + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 2); + + REQUIRE(plan.at(0).spec().name() == "a"); + REQUIRE(plan.at(0).remove_action.has_value()); + + features_check(plan.at(1), "a", {"core", "a1"}); +} + +TEST_CASE ("basic upgrade scheme with self features", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_feature_pgh("a", "a1", "")); + pghs.push_back(make_status_feature_pgh("a", "a2", "a[a1]")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}, {"a2", "a[a1]"}}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + REQUIRE(plan.size() == 2); + + REQUIRE(plan.at(0).spec().name() == "a"); + REQUIRE(plan.at(0).remove_action.has_value()); + + REQUIRE(plan.at(1).spec().name() == "a"); + REQUIRE(plan.at(1).install_action.has_value()); + REQUIRE(plan.at(1).install_action.get()->feature_list == std::set<std::string>{"core", "a1", "a2"}); +} + +TEST_CASE ("basic export scheme", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + REQUIRE(plan.size() == 1); + REQUIRE(plan.at(0).spec.name() == "a"); + REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); +} + +TEST_CASE ("basic export scheme with recurse", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b", "a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + auto spec_b = spec_map.emplace("b", "a"); + + auto plan = Dependencies::create_export_plan({spec_b}, status_db); + + REQUIRE(plan.size() == 2); + REQUIRE(plan.at(0).spec.name() == "a"); + REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + + REQUIRE(plan.at(1).spec.name() == "b"); + REQUIRE(plan.at(1).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); +} + +TEST_CASE ("basic export scheme with bystander", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + auto spec_b = spec_map.emplace("b", "a"); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + REQUIRE(plan.size() == 1); + REQUIRE(plan.at(0).spec.name() == "a"); + REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); +} + +TEST_CASE ("basic export scheme with missing", "[plan]") +{ + StatusParagraphs status_db; + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + REQUIRE(plan.size() == 1); + REQUIRE(plan.at(0).spec.name() == "a"); + REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::NOT_BUILT); +} + +TEST_CASE ("basic export scheme with features", "[plan]") +{ + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("b")); + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_feature_pgh("a", "a1", "b[core]")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + REQUIRE(plan.size() == 2); + + REQUIRE(plan.at(0).spec.name() == "b"); + REQUIRE(plan.at(0).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + + REQUIRE(plan.at(1).spec.name() == "a"); + REQUIRE(plan.at(1).plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); +} diff --git a/toolsrc/src/vcpkg-test/specifier.cpp b/toolsrc/src/vcpkg-test/specifier.cpp new file mode 100644 index 000000000..33df8ba83 --- /dev/null +++ b/toolsrc/src/vcpkg-test/specifier.cpp @@ -0,0 +1,134 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/base/util.h> +#include <vcpkg/packagespec.h> + +using namespace vcpkg; + +TEST_CASE ("specifier conversion", "[specifier]") +{ + SECTION ("full package spec to feature specs") + { + constexpr std::size_t SPEC_SIZE = 6; + + auto a_spec = PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + auto b_spec = PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + auto fspecs = FullPackageSpec::to_feature_specs({{a_spec, {"0", "1"}}, {b_spec, {"2", "3"}}}); + + REQUIRE(fspecs.size() == SPEC_SIZE); + + std::array<const char*, SPEC_SIZE> features = {"", "0", "1", "", "2", "3"}; + std::array<PackageSpec*, SPEC_SIZE> specs = {&a_spec, &a_spec, &a_spec, &b_spec, &b_spec, &b_spec}; + + for (std::size_t i = 0; i < SPEC_SIZE; ++i) + { + REQUIRE(features.at(i) == fspecs.at(i).feature()); + REQUIRE(*specs.at(i) == fspecs.at(i).spec()); + } + } +} + +TEST_CASE ("specifier parsing", "[specifier]") +{ + SECTION ("parsed specifier from string") + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib"); + REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS); + + auto& spec = *maybe_spec.get(); + REQUIRE(spec.name == "zlib"); + REQUIRE(spec.features.size() == 0); + REQUIRE(spec.triplet == ""); + } + + SECTION ("parsed specifier from string with triplet") + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib:x64-uwp"); + REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS); + + auto& spec = *maybe_spec.get(); + REQUIRE(spec.name == "zlib"); + REQUIRE(spec.triplet == "x64-uwp"); + } + + SECTION ("parsed specifier from string with colons") + { + auto ec = vcpkg::ParsedSpecifier::from_string("zlib:x86-uwp:").error(); + REQUIRE(ec == vcpkg::PackageSpecParseResult::TOO_MANY_COLONS); + } + + SECTION ("parsed specifier from string with feature") + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[feature]:x64-uwp"); + REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS); + + auto& spec = *maybe_spec.get(); + REQUIRE(spec.name == "zlib"); + REQUIRE(spec.features.size() == 1); + REQUIRE(spec.features.at(0) == "feature"); + REQUIRE(spec.triplet == "x64-uwp"); + } + + SECTION ("parsed specifier from string with many features") + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[0, 1,2]"); + REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS); + + auto& spec = *maybe_spec.get(); + REQUIRE(spec.name == "zlib"); + REQUIRE(spec.features.size() == 3); + REQUIRE(spec.features.at(0) == "0"); + REQUIRE(spec.features.at(1) == "1"); + REQUIRE(spec.features.at(2) == "2"); + REQUIRE(spec.triplet == ""); + } + + SECTION ("parsed specifier wildcard feature") + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[*]"); + REQUIRE(maybe_spec.error() == vcpkg::PackageSpecParseResult::SUCCESS); + + auto& spec = *maybe_spec.get(); + REQUIRE(spec.name == "zlib"); + REQUIRE(spec.features.size() == 1); + REQUIRE(spec.features.at(0) == "*"); + REQUIRE(spec.triplet == ""); + } + + SECTION ("expand wildcards") + { + auto zlib = vcpkg::FullPackageSpec::from_string("zlib[0,1]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); + auto openssl = + vcpkg::FullPackageSpec::from_string("openssl[*]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); + auto specs = FullPackageSpec::to_feature_specs({zlib, openssl}); + Util::sort(specs); + auto spectargets = FeatureSpec::from_strings_and_triplet( + { + "openssl", + "zlib", + "openssl[*]", + "zlib[0]", + "zlib[1]", + }, + Triplet::X86_UWP); + Util::sort(spectargets); + + REQUIRE(specs.size() == spectargets.size()); + REQUIRE(Util::all_equal(specs, spectargets)); + } + +#if defined(_WIN32) + SECTION ("ASCII to utf16") + { + auto str = vcpkg::Strings::to_utf16("abc"); + REQUIRE(str == L"abc"); + } + + SECTION ("ASCII to utf16 with whitespace") + { + auto str = vcpkg::Strings::to_utf16("abc -x86-windows"); + REQUIRE(str == L"abc -x86-windows"); + } +#endif +} diff --git a/toolsrc/src/vcpkg-test/statusparagraphs.cpp b/toolsrc/src/vcpkg-test/statusparagraphs.cpp new file mode 100644 index 000000000..88b499118 --- /dev/null +++ b/toolsrc/src/vcpkg-test/statusparagraphs.cpp @@ -0,0 +1,110 @@ +#include <catch2/catch.hpp> +#include <vcpkg-test/util.h> + +#include <vcpkg/base/util.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/statusparagraphs.h> + +using namespace vcpkg; +using namespace vcpkg::Paragraphs; +using namespace vcpkg::Test; + +TEST_CASE ("find installed", "[statusparagraphs]") +{ + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed +)"); + + REQUIRE(pghs); + + StatusParagraphs status_db( + Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); + REQUIRE(it != status_db.end()); +} + +TEST_CASE ("find not installed", "[statusparagraphs]") +{ + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: purge ok not-installed +)"); + + REQUIRE(pghs); + + StatusParagraphs status_db( + Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); + REQUIRE(it == status_db.end()); +} + +TEST_CASE ("find with feature packages", "[statusparagraphs]") +{ + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed + +Package: ffmpeg +Feature: openssl +Depends: openssl +Architecture: x64-windows +Multi-Arch: same +Description: +Status: purge ok not-installed +)"); + + REQUIRE(pghs); + + StatusParagraphs status_db( + Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); + REQUIRE(it != status_db.end()); + + // Feature "openssl" is not installed and should not be found + auto it1 = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"}); + REQUIRE(it1 == status_db.end()); +} + +TEST_CASE ("find for feature packages", "[statusparagraphs]") +{ + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed + +Package: ffmpeg +Feature: openssl +Depends: openssl +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed +)"); + REQUIRE(pghs); + + StatusParagraphs status_db( + Util::fmap(*pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + // Feature "openssl" is installed and should therefore be found + auto it = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"}); + REQUIRE(it != status_db.end()); +} diff --git a/toolsrc/src/vcpkg-test/strings.cpp b/toolsrc/src/vcpkg-test/strings.cpp new file mode 100644 index 000000000..d58d1b172 --- /dev/null +++ b/toolsrc/src/vcpkg-test/strings.cpp @@ -0,0 +1,33 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/base/strings.h> + +#include <cstdint> +#include <utility> +#include <vector> + +TEST_CASE ("b32 encoding", "[strings]") +{ + using u64 = std::uint64_t; + + std::vector<std::pair<std::uint64_t, std::string>> map; + + map.emplace_back(0, "AAAAAAAAAAAAA"); + map.emplace_back(1, "BAAAAAAAAAAAA"); + + map.emplace_back(u64(1) << 32, "AAAAAAEAAAAAA"); + map.emplace_back((u64(1) << 32) + 1, "BAAAAAEAAAAAA"); + + map.emplace_back(0xE4D0'1065'D11E'0229, "JRA4RIXMQAUJO"); + map.emplace_back(0xA626'FE45'B135'07FF, "77BKTYWI6XJMK"); + map.emplace_back(0xEE36'D228'0C31'D405, "FAVDDGAFSWN4O"); + map.emplace_back(0x1405'64E7'FE7E'A88C, "MEK5H774ELBIB"); + map.emplace_back(0xFFFF'FFFF'FFFF'FFFF, "777777777777P"); + + std::string result; + for (const auto& pr : map) + { + result = vcpkg::Strings::b32_encode(pr.first); + REQUIRE(vcpkg::Strings::b32_encode(pr.first) == pr.second); + } +} diff --git a/toolsrc/src/vcpkg-test/stringview.cpp b/toolsrc/src/vcpkg-test/stringview.cpp new file mode 100644 index 000000000..4df8e6be5 --- /dev/null +++ b/toolsrc/src/vcpkg-test/stringview.cpp @@ -0,0 +1,17 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/base/stringview.h> + +template <std::size_t N> +static vcpkg::StringView sv(const char (&cstr)[N]) { + return cstr; +} + +TEST_CASE("string view operator==", "[stringview]") { + // these are due to a bug in operator== + // see commit 782723959399a1a0725ac49 + REQUIRE(sv("hey") != sv("heys")); + REQUIRE(sv("heys") != sv("hey")); + REQUIRE(sv("hey") == sv("hey")); + REQUIRE(sv("hey") != sv("hex")); +} diff --git a/toolsrc/src/vcpkg-test/supports.cpp b/toolsrc/src/vcpkg-test/supports.cpp new file mode 100644 index 000000000..f4d8dc65a --- /dev/null +++ b/toolsrc/src/vcpkg-test/supports.cpp @@ -0,0 +1,79 @@ +#include <catch2/catch.hpp> + +#include <vcpkg/sourceparagraph.h> + +using namespace vcpkg; +using Parse::parse_comma_list; + +TEST_CASE ("parse supports all", "[supports]") +{ + auto v = Supports::parse({ + "x64", + "x86", + "arm", + "windows", + "uwp", + "v140", + "v141", + "crt-static", + "crt-dynamic", + }); + + REQUIRE(v.has_value()); + + REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::UWP, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + REQUIRE(v.get()->is_supported(System::CPUArchitecture::ARM, + Supports::Platform::WINDOWS, + Supports::Linkage::STATIC, + Supports::ToolsetVersion::V141)); +} + +TEST_CASE ("parse supports invalid", "[supports]") +{ + auto v = Supports::parse({"arm64"}); + + REQUIRE_FALSE(v.has_value()); + + REQUIRE(v.error().size() == 1); + REQUIRE(v.error().at(0) == "arm64"); +} + +TEST_CASE ("parse supports case sensitive", "[supports]") +{ + auto v = Supports::parse({"Windows"}); + + REQUIRE_FALSE(v.has_value()); + REQUIRE(v.error().size() == 1); + REQUIRE(v.error().at(0) == "Windows"); +} + +TEST_CASE ("parse supports some", "[supports]") +{ + auto v = Supports::parse({ + "x64", + "x86", + "windows", + }); + + REQUIRE(v.has_value()); + + REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::WINDOWS, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + REQUIRE_FALSE(v.get()->is_supported(System::CPUArchitecture::ARM, + Supports::Platform::WINDOWS, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + REQUIRE_FALSE(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::UWP, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + REQUIRE(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::WINDOWS, + Supports::Linkage::STATIC, + Supports::ToolsetVersion::V141)); +} diff --git a/toolsrc/src/vcpkg-test/update.cpp b/toolsrc/src/vcpkg-test/update.cpp new file mode 100644 index 000000000..6f1a87d23 --- /dev/null +++ b/toolsrc/src/vcpkg-test/update.cpp @@ -0,0 +1,102 @@ +#include <catch2/catch.hpp> +#include <vcpkg-test/util.h> + +#include <vcpkg/base/sortedvector.h> + +#include <vcpkg/update.h> + +using namespace vcpkg; +using namespace vcpkg::Update; +using namespace vcpkg::Test; + +using Pgh = std::vector<std::unordered_map<std::string, std::string>>; + +TEST_CASE ("find outdated packages basic", "[update]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFileLocation> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + REQUIRE(pkgs.size() == 1); + REQUIRE(pkgs[0].version_diff.left.to_string() == "2"); + REQUIRE(pkgs[0].version_diff.right.to_string() == "0"); +} + +TEST_CASE ("find outdated packages features", "[update]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + status_paragraphs.push_back(make_status_feature_pgh("a", "b")); + status_paragraphs.back()->package.version = "2"; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFileLocation> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + REQUIRE(pkgs.size() == 1); + REQUIRE(pkgs[0].version_diff.left.to_string() == "2"); + REQUIRE(pkgs[0].version_diff.right.to_string() == "0"); +} + +TEST_CASE ("find outdated packages features 2", "[update]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + status_paragraphs.push_back(make_status_feature_pgh("a", "b")); + status_paragraphs.back()->package.version = "0"; + status_paragraphs.back()->state = InstallState::NOT_INSTALLED; + status_paragraphs.back()->want = Want::PURGE; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFileLocation> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + REQUIRE(pkgs.size() == 1); + REQUIRE(pkgs[0].version_diff.left.to_string() == "2"); + REQUIRE(pkgs[0].version_diff.right.to_string() == "0"); +} + +TEST_CASE ("find outdated packages none", "[update]") +{ + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFileLocation> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "2"}}})); + map.emplace("a", SourceControlFileLocation{std::move(scf), ""}); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + REQUIRE(pkgs.size() == 0); +} diff --git a/toolsrc/src/vcpkg-test/util.cpp b/toolsrc/src/vcpkg-test/util.cpp new file mode 100644 index 000000000..daa21567d --- /dev/null +++ b/toolsrc/src/vcpkg-test/util.cpp @@ -0,0 +1,183 @@ +#include <catch2/catch.hpp> +#include <vcpkg-test/util.h> + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/util.h> +#include <vcpkg/statusparagraph.h> + +// used to get the implementation specific compiler flags (i.e., __cpp_lib_filesystem) +#include <ciso646> + +#include <iostream> +#include <memory> + +#if defined(_WIN32) +#include <windows.h> +#endif + +#define FILESYSTEM_SYMLINK_STD 0 +#define FILESYSTEM_SYMLINK_UNIX 1 +#define FILESYSTEM_SYMLINK_NONE 2 + +#if defined(__cpp_lib_filesystem) + +#define FILESYSTEM_SYMLINK FILESYSTEM_SYMLINK_STD +#include <filesystem> // required for filesystem::create_{directory_}symlink + +#elif !defined(_MSC_VER) + +#define FILESYSTEM_SYMLINK FILESYSTEM_SYMLINK_UNIX +#include <unistd.h> + +#else + +#define FILESYSTEM_SYMLINK FILESYSTEM_SYMLINK_NONE + +#endif + +namespace vcpkg::Test +{ + std::unique_ptr<vcpkg::StatusParagraph> make_status_pgh(const char* name, + const char* depends, + const char* default_features, + const char* triplet) + { + using Pgh = std::unordered_map<std::string, std::string>; + return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, + {"Version", "1"}, + {"Architecture", triplet}, + {"Multi-Arch", "same"}, + {"Depends", depends}, + {"Default-Features", default_features}, + {"Status", "install ok installed"}}); + } + + std::unique_ptr<StatusParagraph> make_status_feature_pgh(const char* name, + const char* feature, + const char* depends, + const char* triplet) + { + using Pgh = std::unordered_map<std::string, std::string>; + return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, + {"Version", "1"}, + {"Feature", feature}, + {"Architecture", triplet}, + {"Multi-Arch", "same"}, + {"Depends", depends}, + {"Status", "install ok installed"}}); + } + + PackageSpec unsafe_pspec(std::string name, Triplet t) + { + auto m_ret = PackageSpec::from_name_and_triplet(name, t); + REQUIRE(m_ret.has_value()); + return m_ret.value_or_exit(VCPKG_LINE_INFO); + } + + static AllowSymlinks internal_can_create_symlinks() noexcept + { +#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_NONE + return AllowSymlinks::No; +#elif FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_UNIX + return AllowSymlinks::Yes; +#elif !defined(_WIN32) // FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_STD + return AllowSymlinks::Yes; +#else + HKEY key; + bool allow_symlinks = true; + + const auto status = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock)", 0, 0, &key); + + if (status == ERROR_FILE_NOT_FOUND) + { + allow_symlinks = false; + std::clog << "Symlinks are not allowed on this system\n"; + } + + if (status == ERROR_SUCCESS) RegCloseKey(key); + + return allow_symlinks ? AllowSymlinks::Yes : AllowSymlinks::No; +#endif + } + const static AllowSymlinks CAN_CREATE_SYMLINKS = internal_can_create_symlinks(); + + AllowSymlinks can_create_symlinks() noexcept { return CAN_CREATE_SYMLINKS; } + + static fs::path internal_base_temporary_directory() + { +#if defined(_WIN32) + wchar_t* tmp = static_cast<wchar_t*>(std::calloc(32'767, 2)); + + if (!GetEnvironmentVariableW(L"TEMP", tmp, 32'767)) + { + std::cerr << "No temporary directory found.\n"; + std::abort(); + } + + fs::path result = tmp; + std::free(tmp); + + return result / L"vcpkg-test"; +#else + return "/tmp/vcpkg-test"; +#endif + } + + const static fs::path BASE_TEMPORARY_DIRECTORY = internal_base_temporary_directory(); + + const fs::path& base_temporary_directory() noexcept { return BASE_TEMPORARY_DIRECTORY; } + +#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_NONE + constexpr char no_filesystem_message[] = + "<filesystem> doesn't exist; on windows, we don't attempt to use the win32 calls to create symlinks"; +#endif + + void create_symlink(const fs::path& target, const fs::path& file, std::error_code& ec) + { +#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_STD + if (can_create_symlinks()) + { + std::filesystem::path targetp = target.native(); + std::filesystem::path filep = file.native(); + + std::filesystem::create_symlink(targetp, filep, ec); + } + else + { + vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Symlinks are not allowed on this system"); + } +#elif FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_UNIX + if (symlink(target.c_str(), file.c_str()) != 0) + { + ec.assign(errno, std::system_category()); + } +#else + Util::unused(target, file, ec); + vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, no_filesystem_message); +#endif + } + + void create_directory_symlink(const fs::path& target, const fs::path& file, std::error_code& ec) + { +#if FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_STD + if (can_create_symlinks()) + { + std::filesystem::path targetp = target.native(); + std::filesystem::path filep = file.native(); + + std::filesystem::create_directory_symlink(targetp, filep, ec); + } + else + { + vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Symlinks are not allowed on this system"); + } +#elif FILESYSTEM_SYMLINK == FILESYSTEM_SYMLINK_UNIX + ::vcpkg::Test::create_symlink(target, file, ec); +#else + Util::unused(target, file, ec); + vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, no_filesystem_message); +#endif + } +} diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index e02bdc71f..9cd0ddf19 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -53,7 +53,7 @@ static constexpr int SURVEY_INTERVAL_IN_HOURS = 24 * 30 * 6; // Initial survey appears after 10 days. Therefore, subtract 24 hours/day * 10 days static constexpr int SURVEY_INITIAL_OFFSET_IN_HOURS = SURVEY_INTERVAL_IN_HOURS - 24 * 10; -void invalid_command(const std::string& cmd) +static void invalid_command(const std::string& cmd) { System::print2(System::Color::error, "invalid command: ", cmd, '\n'); Help::print_usage(); @@ -116,9 +116,17 @@ static void inner(const VcpkgCmdArguments& args) Debug::print("Using vcpkg-root: ", vcpkg_root_dir.u8string(), '\n'); + Optional<fs::path> vcpkg_scripts_root_dir = nullopt; + if (nullptr != args.scripts_root_dir) + { + vcpkg_scripts_root_dir = fs::stdfs::canonical(fs::u8path(*args.scripts_root_dir)); + Debug::print("Using scripts-root: ", vcpkg_scripts_root_dir.value_or_exit(VCPKG_LINE_INFO).u8string(), '\n'); + } + auto default_vs_path = System::get_environment_variable("VCPKG_VISUAL_STUDIO_PATH").value_or(""); - const Expected<VcpkgPaths> expected_paths = VcpkgPaths::create(vcpkg_root_dir, default_vs_path); + const Expected<VcpkgPaths> expected_paths = + VcpkgPaths::create(vcpkg_root_dir, vcpkg_scripts_root_dir, default_vs_path, args.overlay_triplets.get()); Checks::check_exit(VCPKG_LINE_INFO, !expected_paths.error(), "Error: Invalid vcpkg root directory %s: %s", @@ -131,7 +139,11 @@ static void inner(const VcpkgCmdArguments& args) #else const int exit_code = chdir(paths.root.c_str()); #endif - Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Changing the working dir failed"); + Checks::check_exit( + VCPKG_LINE_INFO, + exit_code == 0, + "Changing the working directory to the vcpkg root directory failed. Did you incorrectly define the VCPKG_ROOT " + "environment variable, or did you mistakenly create a file named .vcpkg-root somewhere?"); if (args.command == "install" || args.command == "remove" || args.command == "export" || args.command == "update") { @@ -273,6 +285,8 @@ static std::string trim_path_from_command_line(const std::string& full_command_l #endif #if defined(_WIN32) +// note: this prevents a false positive for -Wmissing-prototypes on clang-cl +int wmain(int, const wchar_t* const*); int wmain(const int argc, const wchar_t* const* const argv) #else int main(const int argc, const char* const* const argv) diff --git a/toolsrc/src/vcpkg/archives.cpp b/toolsrc/src/vcpkg/archives.cpp index 69a916828..d22e841de 100644 --- a/toolsrc/src/vcpkg/archives.cpp +++ b/toolsrc/src/vcpkg/archives.cpp @@ -15,9 +15,10 @@ namespace vcpkg::Archives #endif ; + fs.remove_all(to_path, VCPKG_LINE_INFO); + fs.remove_all(to_path_partial, VCPKG_LINE_INFO); + // TODO: check this error code std::error_code ec; - fs.remove_all(to_path, ec); - fs.remove_all(to_path_partial, ec); fs.create_directories(to_path_partial, ec); const auto ext = archive.extension(); #if defined(_WIN32) diff --git a/toolsrc/src/vcpkg/base/checks.cpp b/toolsrc/src/vcpkg/base/checks.cpp index 0266ad683..c7584258a 100644 --- a/toolsrc/src/vcpkg/base/checks.cpp +++ b/toolsrc/src/vcpkg/base/checks.cpp @@ -27,9 +27,8 @@ namespace vcpkg #if defined(_WIN32) ::TerminateProcess(::GetCurrentProcess(), exit_code); -#else - std::exit(exit_code); #endif + std::exit(exit_code); } void Checks::unreachable(const LineInfo& line_info) diff --git a/toolsrc/src/vcpkg/base/downloads.cpp b/toolsrc/src/vcpkg/base/downloads.cpp index 4bb2178e5..df6b1be09 100644 --- a/toolsrc/src/vcpkg/base/downloads.cpp +++ b/toolsrc/src/vcpkg/base/downloads.cpp @@ -127,7 +127,7 @@ namespace vcpkg::Downloads const fs::path& path, const std::string& sha512) { - std::string actual_hash = vcpkg::Hash::get_file_hash(fs, path, "SHA512"); + std::string actual_hash = vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO, fs, path, Hash::Algorithm::Sha512); // <HACK to handle NuGet.org changing nupkg hashes.> // This is the NEW hash for 7zip diff --git a/toolsrc/src/vcpkg/base/files.cpp b/toolsrc/src/vcpkg/base/files.cpp index 5099795e9..3d7df1a68 100644 --- a/toolsrc/src/vcpkg/base/files.cpp +++ b/toolsrc/src/vcpkg/base/files.cpp @@ -6,10 +6,10 @@ #include <vcpkg/base/system.print.h> #include <vcpkg/base/system.process.h> #include <vcpkg/base/util.h> +#include <vcpkg/base/work_queue.h> -#if defined(__linux__) || defined(__APPLE__) +#if !defined(_WIN32) #include <fcntl.h> -#include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -24,6 +24,123 @@ namespace vcpkg::Files { static const std::regex FILESYSTEM_INVALID_CHARACTERS_REGEX = std::regex(R"([\/:*?"<>|])"); + namespace + { + fs::file_status status_implementation(bool follow_symlinks, const fs::path& p, std::error_code& ec) noexcept + { + using fs::file_type; + using fs::perms; +#if defined(_WIN32) + WIN32_FILE_ATTRIBUTE_DATA file_attributes; + auto ft = file_type::unknown; + auto permissions = perms::unknown; + if (!GetFileAttributesExW(p.c_str(), GetFileExInfoStandard, &file_attributes)) + { + const auto err = GetLastError(); + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + { + ft = file_type::not_found; + } + else + { + ec.assign(err, std::system_category()); + } + } + else if (!follow_symlinks && file_attributes.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + { + // this also gives junctions file_type::directory_symlink + if (file_attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + ft = file_type::directory_symlink; + } + else + { + ft = file_type::symlink; + } + } + else if (file_attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + ft = file_type::directory; + } + else + { + // otherwise, the file is a regular file + ft = file_type::regular; + } + + if (file_attributes.dwFileAttributes & FILE_ATTRIBUTE_READONLY) + { + constexpr auto all_write = perms::group_write | perms::owner_write | perms::others_write; + permissions = perms::all & ~all_write; + } + else if (ft != file_type::none && ft != file_type::none) + { + permissions = perms::all; + } + + return fs::file_status(ft, permissions); + +#else + auto result = follow_symlinks ? fs::stdfs::status(p, ec) : fs::stdfs::symlink_status(p, ec); + // libstdc++ doesn't correctly not-set ec on nonexistent paths + if (ec.value() == ENOENT || ec.value() == ENOTDIR) + { + ec.clear(); + return fs::file_status(file_type::not_found, perms::unknown); + } + return fs::file_status(result.type(), result.permissions()); +#endif + } + + fs::file_status status(const fs::path& p, std::error_code& ec) noexcept + { + return status_implementation(true, p, ec); + } + fs::file_status symlink_status(const fs::path& p, std::error_code& ec) noexcept + { + return status_implementation(false, p, ec); + } + + // does _not_ follow symlinks + void set_writeable(const fs::path& path, std::error_code& ec) noexcept + { +#if defined(_WIN32) + auto const file_name = path.c_str(); + WIN32_FILE_ATTRIBUTE_DATA attributes; + if (!GetFileAttributesExW(file_name, GetFileExInfoStandard, &attributes)) + { + ec.assign(GetLastError(), std::system_category()); + return; + } + + auto dw_attributes = attributes.dwFileAttributes; + dw_attributes &= ~FILE_ATTRIBUTE_READONLY; + if (!SetFileAttributesW(file_name, dw_attributes)) + { + ec.assign(GetLastError(), std::system_category()); + } +#else + struct stat s; + if (lstat(path.c_str(), &s)) + { + ec.assign(errno, std::system_category()); + return; + } + + auto mode = s.st_mode; + // if the file is a symlink, perms don't matter + if (!(mode & S_IFLNK)) + { + mode |= S_IWUSR; + if (chmod(path.c_str(), mode)) + { + ec.assign(errno, std::system_category()); + } + } +#endif + } + } + std::string Filesystem::read_contents(const fs::path& path, LineInfo linfo) const { auto maybe_contents = this->read_contents(path); @@ -56,6 +173,44 @@ namespace vcpkg::Files return r; } + bool Filesystem::exists(const fs::path& path, std::error_code& ec) const + { + return fs::exists(this->symlink_status(path, ec)); + } + + bool Filesystem::exists(LineInfo li, const fs::path& path) const + { + std::error_code ec; + auto result = this->exists(path, ec); + if (ec) Checks::exit_with_message(li, "error checking existence of file %s: %s", path.u8string(), ec.message()); + return result; + } + + bool Filesystem::exists(const fs::path& path) const + { + std::error_code ec; + // drop this on the floor, for compatibility with existing code + return exists(path, ec); + } + + fs::file_status Filesystem::status(vcpkg::LineInfo li, const fs::path& p) const noexcept + { + std::error_code ec; + auto result = this->status(p, ec); + if (ec) vcpkg::Checks::exit_with_message(li, "error getting status of path %s: %s", p.string(), ec.message()); + + return result; + } + + fs::file_status Filesystem::symlink_status(vcpkg::LineInfo li, const fs::path& p) const noexcept + { + std::error_code ec; + auto result = this->symlink_status(p, ec); + if (ec) vcpkg::Checks::exit_with_message(li, "error getting status of path %s: %s", p.string(), ec.message()); + + return result; + } + void Filesystem::write_lines(const fs::path& path, const std::vector<std::string>& lines, LineInfo linfo) { std::error_code ec; @@ -63,6 +218,33 @@ namespace vcpkg::Files if (ec) Checks::exit_with_message(linfo, "error writing lines: %s: %s", path.u8string(), ec.message()); } + void Filesystem::remove_all(const fs::path& path, LineInfo li) + { + std::error_code ec; + fs::path failure_point; + + this->remove_all(path, ec, failure_point); + + if (ec) + { + Checks::exit_with_message(li, + "Failure to remove_all(%s) due to file %s: %s", + path.string(), + failure_point.string(), + ec.message()); + } + } + + fs::path Filesystem::canonical(LineInfo li, const fs::path& path) const + { + std::error_code ec; + + const auto result = this->canonical(path, ec); + + if (ec) Checks::exit_with_message(li, "Error getting canonicalization of %s: %s", path.string(), ec.message()); + return result; + } + struct RealFilesystem final : Filesystem { virtual Expected<std::string> read_contents(const fs::path& file_path) const override @@ -87,7 +269,7 @@ namespace vcpkg::Files file_stream.read(&output[0], length); file_stream.close(); - return std::move(output); + return output; } virtual Expected<std::vector<std::string>> read_lines(const fs::path& file_path) const override { @@ -101,17 +283,20 @@ namespace vcpkg::Files std::string line; while (std::getline(file_stream, line)) { + // Remove the trailing \r to accomodate Windows line endings. + if ((!line.empty()) && (line.back() == '\r')) line.pop_back(); + output.push_back(line); } file_stream.close(); - return std::move(output); + return output; } virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const std::string& filename) const override { fs::path current_dir = starting_dir; - if (exists(current_dir / filename)) + if (exists(VCPKG_LINE_INFO, current_dir / filename)) { return current_dir; } @@ -136,7 +321,7 @@ namespace vcpkg::Files current_dir = std::move(parent); const fs::path candidate = current_dir / filename; - if (exists(candidate)) + if (exists(VCPKG_LINE_INFO, candidate)) { return current_dir; } @@ -211,7 +396,7 @@ namespace vcpkg::Files { this->rename(oldpath, newpath, ec); Util::unused(temp_suffix); -#if defined(__linux__) || defined(__APPLE__) +#if !defined(_WIN32) if (ec) { auto dst = newpath; @@ -234,6 +419,33 @@ namespace vcpkg::Files auto written_bytes = sendfile(o_fd, i_fd, &bytes, info.st_size); #elif defined(__APPLE__) auto written_bytes = fcopyfile(i_fd, o_fd, 0, COPYFILE_ALL); +#else + ssize_t written_bytes = 0; + { + constexpr std::size_t buffer_length = 4096; + auto buffer = std::make_unique<unsigned char[]>(buffer_length); + while (auto read_bytes = read(i_fd, buffer.get(), buffer_length)) + { + if (read_bytes == -1) + { + written_bytes = -1; + break; + } + auto remaining = read_bytes; + while (remaining > 0) { + auto read_result = write(o_fd, buffer.get(), remaining); + if (read_result == -1) + { + written_bytes = -1; + // break two loops + goto copy_failure; + } + remaining -= read_result; + } + } + + copy_failure: ; + } #endif if (written_bytes == -1) { @@ -254,30 +466,132 @@ namespace vcpkg::Files #endif } virtual bool remove(const fs::path& path, std::error_code& ec) override { return fs::stdfs::remove(path, ec); } - virtual std::uintmax_t remove_all(const fs::path& path, std::error_code& ec) override + virtual void remove_all(const fs::path& path, std::error_code& ec, fs::path& failure_point) override { - // Working around the currently buggy remove_all() - std::uintmax_t out = fs::stdfs::remove_all(path, ec); + /* + does not use the std::experimental::filesystem call since this is + quite a bit faster, and also supports symlinks + */ - for (int i = 0; i < 5 && this->exists(path); i++) + struct remove { - using namespace std::chrono_literals; - std::this_thread::sleep_for(i * 100ms); - out += fs::stdfs::remove_all(path, ec); - } + struct ErrorInfo : Util::ResourceBase + { + std::error_code ec; + fs::path failure_point; + }; + /* + if `current_path` is a directory, first `remove`s all + elements of the directory, then removes current_path. + + else if `current_path` exists, removes current_path + + else does nothing + */ + static void do_remove(const fs::path& current_path, ErrorInfo& err) + { + std::error_code ec; + const auto path_status = Files::symlink_status(current_path, ec); + if (check_ec(ec, current_path, err)) return; + if (!fs::exists(path_status)) return; + + const auto path_type = path_status.type(); + + if ((path_status.permissions() & fs::perms::owner_write) != fs::perms::owner_write) + { + set_writeable(current_path, ec); + if (check_ec(ec, current_path, err)) return; + } - if (this->exists(path)) + if (path_type == fs::file_type::directory) + { + for (const auto& entry : fs::stdfs::directory_iterator(current_path)) + { + do_remove(entry, err); + if (err.ec) return; + } +#if defined(_WIN32) + if (!RemoveDirectoryW(current_path.c_str())) + { + ec.assign(GetLastError(), std::system_category()); + } +#else + if (rmdir(current_path.c_str())) + { + ec.assign(errno, std::system_category()); + } +#endif + } +#if defined(_WIN32) + else if (path_type == fs::file_type::directory_symlink) + { + if (!RemoveDirectoryW(current_path.c_str())) + { + ec.assign(GetLastError(), std::system_category()); + } + } + else + { + if (!DeleteFileW(current_path.c_str())) + { + ec.assign(GetLastError(), std::system_category()); + } + } +#else + else + { + if (unlink(current_path.c_str())) + { + ec.assign(errno, std::system_category()); + } + } +#endif + + check_ec(ec, current_path, err); + } + + static bool check_ec(const std::error_code& ec, const fs::path& current_path, ErrorInfo& err) + { + if (ec) + { + err.ec = ec; + err.failure_point = current_path; + + return true; + } + else + { + return false; + } + } + }; + + /* + we need to do backoff on the removal of the top level directory, + so we can only delete the directory after all the + lower levels have been deleted. + */ + + remove::ErrorInfo err; + for (int backoff = 0; backoff < 5; ++backoff) { - System::print2( - System::Color::warning, - "Some files in ", - path.u8string(), - " were unable to be removed. Close any editors operating in this directory and retry.\n"); + if (backoff) + { + using namespace std::chrono_literals; + auto backoff_time = 100ms * backoff; + std::this_thread::sleep_for(backoff_time); + } + + remove::do_remove(path, err); + if (!err.ec) + { + break; + } } - return out; + ec = std::move(err.ec); + failure_point = std::move(err.failure_point); } - virtual bool exists(const fs::path& path) const override { return fs::stdfs::exists(path); } virtual bool is_directory(const fs::path& path) const override { return fs::stdfs::is_directory(path); } virtual bool is_regular_file(const fs::path& path) const override { return fs::stdfs::is_regular_file(path); } virtual bool is_empty(const fs::path& path) const override { return fs::stdfs::is_empty(path); } @@ -307,11 +621,11 @@ namespace vcpkg::Files virtual fs::file_status status(const fs::path& path, std::error_code& ec) const override { - return fs::stdfs::status(path, ec); + return Files::status(path, ec); } virtual fs::file_status symlink_status(const fs::path& path, std::error_code& ec) const override { - return fs::stdfs::symlink_status(path, ec); + return Files::symlink_status(path, ec); } virtual void write_contents(const fs::path& file_path, const std::string& data, std::error_code& ec) override { @@ -342,6 +656,11 @@ namespace vcpkg::Files } } + virtual fs::path canonical(const fs::path& path, std::error_code& ec) const override + { + return fs::stdfs::canonical(path, ec); + } + virtual std::vector<fs::path> find_from_PATH(const std::string& name) const override { #if defined(_WIN32) @@ -349,13 +668,14 @@ namespace vcpkg::Files auto paths = Strings::split(System::get_environment_variable("PATH").value_or_exit(VCPKG_LINE_INFO), ";"); std::vector<fs::path> ret; + std::error_code ec; for (auto&& path : paths) { auto base = path + "/" + name; for (auto&& ext : EXTS) { auto p = fs::u8path(base + ext.c_str()); - if (Util::find(ret, p) == ret.end() && this->exists(p)) + if (Util::find(ret, p) == ret.end() && this->exists(p, ec)) { ret.push_back(p); Debug::print("Found path: ", p.u8string(), '\n'); diff --git a/toolsrc/src/vcpkg/base/hash.cpp b/toolsrc/src/vcpkg/base/hash.cpp index 310b8c35e..11df3e329 100644 --- a/toolsrc/src/vcpkg/base/hash.cpp +++ b/toolsrc/src/vcpkg/base/hash.cpp @@ -1,5 +1,7 @@ #include "pch.h" +#include <vcpkg/base/hash.h> + #include <vcpkg/base/checks.h> #include <vcpkg/base/strings.h> #include <vcpkg/base/system.process.h> @@ -12,236 +14,758 @@ #ifndef NT_SUCCESS #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) #endif + #endif namespace vcpkg::Hash { - static void verify_has_only_allowed_chars(const std::string& s) + using uchar = unsigned char; + + Optional<Algorithm> algorithm_from_string(StringView sv) noexcept + { + if (Strings::case_insensitive_ascii_equals(sv, "SHA1")) + { + return {Algorithm::Sha1}; + } + if (Strings::case_insensitive_ascii_equals(sv, "SHA256")) + { + return {Algorithm::Sha256}; + } + if (Strings::case_insensitive_ascii_equals(sv, "SHA512")) + { + return {Algorithm::Sha512}; + } + + return {}; + } + + const char* to_string(Algorithm algo) noexcept { - 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); + switch (algo) + { + case Algorithm::Sha1: return "SHA1"; + case Algorithm::Sha256: return "SHA256"; + case Algorithm::Sha512: return "SHA512"; + default: vcpkg::Checks::exit_fail(VCPKG_LINE_INFO); + } } -#if defined(_WIN32) + namespace { - std::string to_hex(const unsigned char* string, const size_t bytes) + struct UInt128 { - static constexpr char HEX_MAP[] = "0123456789abcdef"; + std::uint64_t top; + std::uint64_t bottom; + + UInt128() = default; + UInt128(std::uint64_t value) : top(0), bottom(value) {} + + UInt128& operator<<=(int by) noexcept + { + if (by == 0) + { + return *this; + } + + if (by < 64) + { + top <<= by; + const auto shift_up = bottom >> (64 - by); + top |= shift_up; + bottom <<= by; + } + else + { + top = bottom; + top <<= (by - 64); + } + + return *this; + } + + UInt128& operator>>=(int by) noexcept + { + if (by == 0) + { + return *this; + } + + if (by < 64) + { + bottom >>= by; + const auto shift_down = top << (64 - by); + bottom |= shift_down; + top >>= by; + } + else + { + bottom = top; + bottom >>= (by - 64); + } + + return *this; + } + + UInt128& operator+=(std::size_t lhs) noexcept + { + // bottom + lhs > uint64::max + if (bottom > std::numeric_limits<std::uint64_t>::max() - lhs) + { + top += 1; + } + bottom += lhs; + return *this; + } + }; + + } + template<class T> + void top_bits(T) = delete; - std::string output; - output.resize(2 * bytes); + static constexpr uchar top_bits(uchar x) noexcept { return x; } + static constexpr uchar top_bits(std::uint32_t x) noexcept { return (x >> 24) & 0xFF; } + static constexpr uchar top_bits(std::uint64_t x) noexcept { return (x >> 56) & 0xFF; } + static constexpr uchar top_bits(UInt128 x) noexcept { return top_bits(x.top); } - size_t current_char = 0; - for (size_t i = 0; i < bytes; i++) + // treats UIntTy as big endian for the purpose of this mapping + template<class UIntTy> + static std::string to_hex(const UIntTy* start, const UIntTy* end) noexcept + { + static constexpr char HEX_MAP[] = "0123456789abcdef"; + + std::string output; + output.resize(2 * sizeof(UIntTy) * (end - start)); + + std::size_t output_index = 0; + for (const UIntTy* it = start; it != end; ++it) + { + // holds *it in a big-endian buffer, for copying into output + uchar buff[sizeof(UIntTy)]; + UIntTy tmp = *it; + for (uchar& ch : buff) + { + ch = top_bits(tmp); + tmp <<= 8; + } + + for (const auto byte : buff) { // high - output[current_char] = HEX_MAP[(string[i] & 0xF0) >> 4]; - ++current_char; + output[output_index] = HEX_MAP[(byte & 0xF0) >> 4]; + ++output_index; // low - output[current_char] = HEX_MAP[(string[i] & 0x0F)]; - ++current_char; + output[output_index] = HEX_MAP[byte & 0x0F]; + ++output_index; } - - return output; } - class BCryptHasher + return output; + } + + namespace + { +#if defined(_WIN32) + BCRYPT_ALG_HANDLE get_alg_handle(LPCWSTR algorithm_identifier) noexcept { - struct BCryptAlgorithmHandle : Util::ResourceBase + BCRYPT_ALG_HANDLE result; + auto error = BCryptOpenAlgorithmProvider(&result, algorithm_identifier, nullptr, 0); + if (!NT_SUCCESS(error)) { - BCRYPT_ALG_HANDLE handle = nullptr; + Checks::exit_with_message(VCPKG_LINE_INFO, "Failure to open algorithm: %ls", algorithm_identifier); + } + + return result; + } - ~BCryptAlgorithmHandle() + struct BCryptHasher : Hasher + { + static const BCRYPT_ALG_HANDLE sha1_alg_handle; + static const BCRYPT_ALG_HANDLE sha256_alg_handle; + static const BCRYPT_ALG_HANDLE sha512_alg_handle; + + explicit BCryptHasher(Algorithm algo) noexcept + { + switch (algo) { - if (handle) BCryptCloseAlgorithmProvider(handle, 0); + case Algorithm::Sha1: alg_handle = sha1_alg_handle; break; + case Algorithm::Sha256: alg_handle = sha256_alg_handle; break; + case Algorithm::Sha512: alg_handle = sha512_alg_handle; break; + default: Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown algorithm"); } - }; - struct BCryptHashHandle : Util::ResourceBase + clear(); + } + + virtual void add_bytes(const void* start_, const void* end_) noexcept override { - BCRYPT_HASH_HANDLE handle = nullptr; + // BCryptHashData takes its input as non-const, but does not modify it + uchar* start = const_cast<uchar*>(static_cast<const uchar*>(start_)); + const uchar* end = static_cast<const uchar*>(end_); + Checks::check_exit(VCPKG_LINE_INFO, end - start >= 0); + + // only matters on 64-bit -- BCryptHasher takes an unsigned long + // length, so if you have an array bigger than 2**32-1 elements, + // you have a problem. +#if defined(_M_AMD64) || defined(_M_ARM64) + constexpr std::ptrdiff_t max = std::numeric_limits<unsigned long>::max(); + Checks::check_exit(VCPKG_LINE_INFO, end - start <= max); +#endif - ~BCryptHashHandle() - { - if (handle) BCryptDestroyHash(handle); - } - }; + const auto length = static_cast<unsigned long>(end - start); + const NTSTATUS error_code = BCryptHashData(hash_handle, start, length, 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to process a chunk"); + } - static void initialize_hash_handle(BCryptHashHandle& hash_handle, - const BCryptAlgorithmHandle& algorithm_handle) + virtual void clear() noexcept override { - const NTSTATUS error_code = - BCryptCreateHash(algorithm_handle.handle, &hash_handle.handle, nullptr, 0, nullptr, 0, 0); + if (hash_handle) BCryptDestroyHash(hash_handle); + const NTSTATUS error_code = BCryptCreateHash(alg_handle, &hash_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) + virtual std::string get_hash() noexcept override { - 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"); + const auto hash_size = get_hash_buffer_size(); + const auto buffer = std::make_unique<uchar[]>(hash_size); + const auto hash = buffer.get(); + + const NTSTATUS error_code = BCryptFinishHash(hash_handle, hash, hash_size, 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to finalize the hash"); + return to_hex(hash, hash + hash_size); } - static std::string finalize_hash_handle(const BCryptHashHandle& hash_handle, const ULONG length_in_bytes) + ~BCryptHasher() { BCryptDestroyHash(hash_handle); } + + private: + unsigned long get_hash_buffer_size() const { - 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(std::string hash_type) - { - NTSTATUS error_code = BCryptOpenAlgorithmProvider( - &this->algorithm_handle.handle, - Strings::to_utf16(Strings::ascii_to_uppercase(std::move(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); + unsigned long hash_buffer_bytes; + unsigned long cb_data; + const NTSTATUS error_code = BCryptGetProperty(alg_handle, + BCRYPT_HASH_LENGTH, + reinterpret_cast<uchar*>(&hash_buffer_bytes), + sizeof(hash_buffer_bytes), + &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; + + return hash_buffer_bytes; } - std::string hash_file(const fs::path& path) const - { - BCryptHashHandle hash_handle; - initialize_hash_handle(hash_handle, this->algorithm_handle); + BCRYPT_HASH_HANDLE hash_handle = nullptr; + BCRYPT_ALG_HANDLE alg_handle = nullptr; + }; + + const BCRYPT_ALG_HANDLE BCryptHasher::sha1_alg_handle = get_alg_handle(BCRYPT_SHA1_ALGORITHM); + const BCRYPT_ALG_HANDLE BCryptHasher::sha256_alg_handle = get_alg_handle(BCRYPT_SHA256_ALGORITHM); + const BCRYPT_ALG_HANDLE BCryptHasher::sha512_alg_handle = get_alg_handle(BCRYPT_SHA512_ALGORITHM); +#else + + template<class WordTy> + static WordTy shl(WordTy value, int by) noexcept + { + return value << by; + } + + static std::uint32_t shr32(std::uint32_t value, int by) noexcept { return value >> by; } + static std::uint32_t rol32(std::uint32_t value, int by) noexcept + { + return (value << by) | (value >> (32 - by)); + } + static std::uint32_t ror32(std::uint32_t value, int by) noexcept + { + return (value >> by) | (value << (32 - by)); + } + + static std::uint64_t shr64(std::uint64_t value, int by) noexcept { return value >> by; } + static std::uint64_t ror64(std::uint64_t value, int by) noexcept + { + return (value >> by) | (value << (64 - by)); + } + + template<class ShaAlgorithm> + struct ShaHasher final : Hasher + { + ShaHasher() = default; - 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) + virtual void add_bytes(const void* start, const void* end) noexcept override + { + for (;;) { - unsigned char buffer[4096]; - while (const auto actual_size = fread(buffer, 1, sizeof(buffer), file)) + start = add_to_unprocessed(start, end); + if (!start) { - hash_data(hash_handle, buffer, actual_size); + break; // done } - fclose(file); + + m_impl.process_full_chunk(m_chunk); + m_current_chunk_size = 0; } + } + + virtual void clear() noexcept override + { + m_impl.clear(); - return finalize_hash_handle(hash_handle, length_in_bytes); + // m_chunk is theoretically uninitialized, so no need to reset it + m_current_chunk_size = 0; + m_message_length = 0; } - std::string hash_string(const std::string& s) const + virtual std::string get_hash() noexcept override { - 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); + process_last_chunk(); + return to_hex(m_impl.begin(), m_impl.end()); } private: - BCryptAlgorithmHandle algorithm_handle; - ULONG length_in_bytes; - }; - } + // if unprocessed gets filled, + // returns a pointer to the remainder of the block (which might be end) + // else, returns nullptr + const void* add_to_unprocessed(const void* start_, const void* end_) noexcept + { + const uchar* start = static_cast<const uchar*>(start_); + const uchar* end = static_cast<const uchar*>(end_); - std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type) - { - Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string()); - return BCryptHasher{hash_type}.hash_file(path); - } + const auto remaining = chunk_size - m_current_chunk_size; - 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); - } + const std::size_t message_length = end - start; + if (message_length >= remaining) + { + std::copy(start, start + remaining, chunk_begin()); + m_current_chunk_size += remaining; + m_message_length += remaining * 8; + return start + remaining; + } + else + { + std::copy(start, end, chunk_begin()); + m_current_chunk_size += message_length; + m_message_length += message_length * 8; + return nullptr; + } + } -#else - static std::string get_digest_size(const std::string& hash_type) - { - if (!Strings::case_insensitive_ascii_starts_with(hash_type, "SHA")) + // called before `get_hash` + void process_last_chunk() noexcept + { + const auto message_length = m_message_length; + + // append the bit '1' to the message + { + const uchar temp = 0x80; + add_to_unprocessed(&temp, &temp + 1); + } + + // append 0 to the message so that the resulting length is just enough + // to add the message length + if (chunk_size - m_current_chunk_size < sizeof(m_message_length)) + { + // not enough space to add the message length + // just resize and process full chunk + std::fill(chunk_begin(), m_chunk.end(), static_cast<uchar>(0)); + m_impl.process_full_chunk(m_chunk); + m_current_chunk_size = 0; + } + + const auto before_length = m_chunk.end() - sizeof(m_message_length); + std::fill(chunk_begin(), before_length, static_cast<uchar>(0)); + std::generate(before_length, m_chunk.end(), [length = message_length]() mutable { + const auto result = top_bits(length); + length <<= 8; + return result; + }); + + m_impl.process_full_chunk(m_chunk); + } + + auto chunk_begin() { return m_chunk.begin() + m_current_chunk_size; } + + using underlying_type = typename ShaAlgorithm::underlying_type; + using message_length_type = typename ShaAlgorithm::message_length_type; + constexpr static std::size_t chunk_size = ShaAlgorithm::chunk_size; + + ShaAlgorithm m_impl{}; + + std::array<uchar, chunk_size> m_chunk{}; + std::size_t m_current_chunk_size = 0; + message_length_type m_message_length = 0; + }; + template<class WordTy> + inline void sha_fill_initial_words(const uchar* chunk, WordTy* words) { - Checks::exit_with_message( - VCPKG_LINE_INFO, "shasum only supports SHA hashes, but %s was provided", hash_type); + // break chunk into 16 N-bit words + for (std::size_t word = 0; word < 16; ++word) + { + words[word] = 0; + // big-endian -- so the earliest i becomes the most significant + for (std::size_t byte = 0; byte < sizeof(WordTy); ++byte) + { + const auto bits_to_shift = static_cast<int>(8 * (sizeof(WordTy) - 1 - byte)); + words[word] |= shl<WordTy>(chunk[word * sizeof(WordTy) + byte], bits_to_shift); + } + } } - return hash_type.substr(3, hash_type.length() - 3); - } + struct Sha1Algorithm + { + using underlying_type = std::uint32_t; + using message_length_type = std::uint64_t; + constexpr static std::size_t chunk_size = 64; // = 512 / 8 + constexpr static std::size_t number_of_rounds = 80; - static std::string parse_shasum_output(const std::string& shasum_output) - { - std::vector<std::string> split = Strings::split(shasum_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)", - shasum_output, - std::to_string(split.size())); - - return split[0]; - } + Sha1Algorithm() noexcept { clear(); } - std::string get_file_hash(const Files::Filesystem& fs, 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, fs.exists(path), "File %s does not exist", path.u8string()); + void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept + { + std::uint32_t words[80]; + + sha_fill_initial_words(&chunk[0], words); + for (std::size_t i = 16; i < number_of_rounds; ++i) + { + const auto sum = words[i - 3] ^ words[i - 8] ^ words[i - 14] ^ words[i - 16]; + words[i] = rol32(sum, 1); + } + + std::uint32_t a = m_digest[0]; + std::uint32_t b = m_digest[1]; + std::uint32_t c = m_digest[2]; + std::uint32_t d = m_digest[3]; + std::uint32_t e = m_digest[4]; + + for (std::size_t i = 0; i < number_of_rounds; ++i) + { + std::uint32_t f; + std::uint32_t k; + + if (i < 20) + { + f = (b & c) | (~b & d); + k = 0x5A827999; + } + else if (i < 40) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (i < 60) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + + auto tmp = rol32(a, 5) + f + e + k + words[i]; + e = d; + d = c; + c = rol32(b, 30); + b = a; + a = tmp; + } + + m_digest[0] += a; + m_digest[1] += b; + m_digest[2] += c; + m_digest[3] += d; + m_digest[4] += e; + } - // Try hash-specific tools, like sha512sum + void clear() noexcept + { + m_digest[0] = 0x67452301; + m_digest[1] = 0xEFCDAB89; + m_digest[2] = 0x98BADCFE; + m_digest[3] = 0x10325476; + m_digest[4] = 0xC3D2E1F0; + } + + const std::uint32_t* begin() const noexcept { return &m_digest[0]; } + const std::uint32_t* end() const noexcept { return &m_digest[5]; } + + std::uint32_t m_digest[5]; + }; + + struct Sha256Algorithm { - const auto ec_data = System::cmd_execute_and_capture_output( - Strings::format(R"(sha%ssum "%s")", digest_size, path.u8string())); - if (ec_data.exit_code == 0) + using underlying_type = std::uint32_t; + using message_length_type = std::uint64_t; + constexpr static std::size_t chunk_size = 64; + + constexpr static std::size_t number_of_rounds = 64; + + Sha256Algorithm() noexcept { clear(); } + + void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept { - return parse_shasum_output(ec_data.output); + std::uint32_t words[64]; + + sha_fill_initial_words(&chunk[0], words); + + for (std::size_t i = 16; i < number_of_rounds; ++i) + { + const auto w0 = words[i - 15]; + const auto s0 = ror32(w0, 7) ^ ror32(w0, 18) ^ shr32(w0, 3); + const auto w1 = words[i - 2]; + const auto s1 = ror32(w1, 17) ^ ror32(w1, 19) ^ shr32(w1, 10); + words[i] = words[i - 16] + s0 + words[i - 7] + s1; + } + + std::uint32_t local[8]; + std::copy(begin(), end(), std::begin(local)); + + for (std::size_t i = 0; i < number_of_rounds; ++i) + { + const auto a = local[0]; + const auto b = local[1]; + const auto c = local[2]; + + const auto s0 = ror32(a, 2) ^ ror32(a, 13) ^ ror32(a, 22); + const auto maj = (a & b) ^ (a & c) ^ (b & c); + const auto tmp1 = s0 + maj; + + const auto e = local[4]; + + const auto s1 = ror32(e, 6) ^ ror32(e, 11) ^ ror32(e, 25); + const auto ch = (e & local[5]) ^ (~e & local[6]); + const auto tmp2 = local[7] + s1 + ch + round_constants[i] + words[i]; + + for (std::size_t j = 7; j > 0; --j) + { + local[j] = local[j - 1]; + } + local[4] += tmp2; + local[0] = tmp1 + tmp2; + } + + for (std::size_t i = 0; i < 8; ++i) + { + m_digest[i] += local[i]; + } } - } - // Try shasum + void clear() noexcept + { + m_digest[0] = 0x6a09e667; + m_digest[1] = 0xbb67ae85; + m_digest[2] = 0x3c6ef372; + m_digest[3] = 0xa54ff53a; + m_digest[4] = 0x510e527f; + m_digest[5] = 0x9b05688c; + m_digest[6] = 0x1f83d9ab; + m_digest[7] = 0x5be0cd19; + } + + constexpr static std::array<std::uint32_t, number_of_rounds> round_constants = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + + std::uint32_t* begin() noexcept { return &m_digest[0]; } + std::uint32_t* end() noexcept { return &m_digest[8]; } + + std::uint32_t m_digest[8]; + }; + + struct Sha512Algorithm { - const auto ec_data = System::cmd_execute_and_capture_output( - Strings::format(R"(shasum -a %s "%s")", digest_size, path.u8string())); - if (ec_data.exit_code == 0) + using underlying_type = std::uint64_t; + using message_length_type = UInt128; + constexpr static std::size_t chunk_size = 128; // = 1024 / 8 + + constexpr static std::size_t number_of_rounds = 80; + + Sha512Algorithm() noexcept { clear(); } + + void process_full_chunk(const std::array<uchar, chunk_size>& chunk) noexcept + { + std::uint64_t words[80]; + + sha_fill_initial_words(&chunk[0], words); + + for (std::size_t i = 16; i < number_of_rounds; ++i) + { + const auto w0 = words[i - 15]; + const auto s0 = ror64(w0, 1) ^ ror64(w0, 8) ^ shr64(w0, 7); + const auto w1 = words[i - 2]; + const auto s1 = ror64(w1, 19) ^ ror64(w1, 61) ^ shr64(w1, 6); + words[i] = words[i - 16] + s0 + words[i - 7] + s1; + } + + std::uint64_t local[8]; + std::copy(begin(), end(), std::begin(local)); + + for (std::size_t i = 0; i < number_of_rounds; ++i) + { + const auto a = local[0]; + const auto b = local[1]; + const auto c = local[2]; + + const auto s0 = ror64(a, 28) ^ ror64(a, 34) ^ ror64(a, 39); + const auto maj = (a & b) ^ (a & c) ^ (b & c); + const auto tmp0 = s0 + maj; + + const auto e = local[4]; + + const auto s1 = ror64(e, 14) ^ ror64(e, 18) ^ ror64(e, 41); + const auto ch = (e & local[5]) ^ (~e & local[6]); + const auto tmp1 = local[7] + s1 + ch + round_constants[i] + words[i]; + + for (std::size_t j = 7; j > 0; --j) + { + local[j] = local[j - 1]; + } + local[4] += tmp1; + local[0] = tmp0 + tmp1; + } + + for (std::size_t i = 0; i < 8; ++i) + { + m_digest[i] += local[i]; + } + } + + void clear() noexcept { - return parse_shasum_output(ec_data.output); + m_digest[0] = 0x6a09e667f3bcc908; + m_digest[1] = 0xbb67ae8584caa73b; + m_digest[2] = 0x3c6ef372fe94f82b; + m_digest[3] = 0xa54ff53a5f1d36f1; + m_digest[4] = 0x510e527fade682d1; + m_digest[5] = 0x9b05688c2b3e6c1f; + m_digest[6] = 0x1f83d9abfb41bd6b; + m_digest[7] = 0x5be0cd19137e2179; } - } - Checks::exit_with_message(VCPKG_LINE_INFO, "Could not hash file %s with %s", path.u8string(), hash_type); + constexpr static std::array<std::uint64_t, number_of_rounds> round_constants = { + 0x428a2f98d728ae22, 0x7137449123ef65cd, 0xb5c0fbcfec4d3b2f, 0xe9b5dba58189dbbc, 0x3956c25bf348b538, + 0x59f111f1b605d019, 0x923f82a4af194f9b, 0xab1c5ed5da6d8118, 0xd807aa98a3030242, 0x12835b0145706fbe, + 0x243185be4ee4b28c, 0x550c7dc3d5ffb4e2, 0x72be5d74f27b896f, 0x80deb1fe3b1696b1, 0x9bdc06a725c71235, + 0xc19bf174cf692694, 0xe49b69c19ef14ad2, 0xefbe4786384f25e3, 0x0fc19dc68b8cd5b5, 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, 0x4a7484aa6ea6e483, 0x5cb0a9dcbd41fbd4, 0x76f988da831153b5, 0x983e5152ee66dfab, + 0xa831c66d2db43210, 0xb00327c898fb213f, 0xbf597fc7beef0ee4, 0xc6e00bf33da88fc2, 0xd5a79147930aa725, + 0x06ca6351e003826f, 0x142929670a0e6e70, 0x27b70a8546d22ffc, 0x2e1b21385c26c926, 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, 0x650a73548baf63de, 0x766a0abb3c77b2a8, 0x81c2c92e47edaee6, 0x92722c851482353b, + 0xa2bfe8a14cf10364, 0xa81a664bbc423001, 0xc24b8b70d0f89791, 0xc76c51a30654be30, 0xd192e819d6ef5218, + 0xd69906245565a910, 0xf40e35855771202a, 0x106aa07032bbd1b8, 0x19a4c116b8d2d0c8, 0x1e376c085141ab53, + 0x2748774cdf8eeb99, 0x34b0bcb5e19b48a8, 0x391c0cb3c5c95a63, 0x4ed8aa4ae3418acb, 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, 0x748f82ee5defb2fc, 0x78a5636f43172f60, 0x84c87814a1f0ab72, 0x8cc702081a6439ec, + 0x90befffa23631e28, 0xa4506cebde82bde9, 0xbef9a3f7b2c67915, 0xc67178f2e372532b, 0xca273eceea26619c, + 0xd186b8c721c0c207, 0xeada7dd6cde0eb1e, 0xf57d4f7fee6ed178, 0x06f067aa72176fba, 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, 0x1b710b35131c471b, 0x28db77f523047d84, 0x32caab7b40c72493, 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, 0x4cc5d4becb3e42b6, 0x597f299cfc657e2a, 0x5fcb6fab3ad6faec, 0x6c44198c4a475817}; + + std::uint64_t* begin() noexcept { return &m_digest[0]; } + std::uint64_t* end() noexcept { return &m_digest[8]; } + + std::uint64_t m_digest[8]; + }; + + // This is required on older compilers, since it was required in C++14 + constexpr std::array<std::uint32_t, Sha256Algorithm::number_of_rounds> Sha256Algorithm::round_constants; + constexpr std::array<std::uint64_t, Sha512Algorithm::number_of_rounds> Sha512Algorithm::round_constants; +#endif } - std::string get_string_hash(const std::string& s, const std::string& hash_type) + std::unique_ptr<Hasher> get_hasher_for(Algorithm algo) noexcept { - const std::string digest_size = get_digest_size(hash_type); - verify_has_only_allowed_chars(s); +#if defined(_WIN32) + return std::make_unique<BCryptHasher>(algo); +#else + switch (algo) + { + case Algorithm::Sha1: return std::make_unique<ShaHasher<Sha1Algorithm>>(); + case Algorithm::Sha256: return std::make_unique<ShaHasher<Sha256Algorithm>>(); + case Algorithm::Sha512: return std::make_unique<ShaHasher<Sha512Algorithm>>(); + default: vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown hashing algorithm: %s", algo); + } +#endif + } - // Try hash-specific tools, like sha512sum + template<class F> + static std::string do_hash(Algorithm algo, const F& f) noexcept + { +#if defined(_WIN32) + auto hasher = BCryptHasher(algo); + return f(hasher); +#else + switch (algo) { - const auto ec_data = - System::cmd_execute_and_capture_output(Strings::format(R"(echo -n "%s" | sha%ssum)", s, digest_size)); - if (ec_data.exit_code == 0) + case Algorithm::Sha1: + { + auto hasher = ShaHasher<Sha1Algorithm>(); + return f(hasher); + } + case Algorithm::Sha256: + { + auto hasher = ShaHasher<Sha256Algorithm>(); + return f(hasher); + } + case Algorithm::Sha512: { - return parse_shasum_output(ec_data.output); + auto hasher = ShaHasher<Sha512Algorithm>(); + return f(hasher); } + default: vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown hashing algorithm: %s", algo); } +#endif + } + + std::string get_bytes_hash(const void* first, const void* last, Algorithm algo) noexcept + { + return do_hash(algo, [first, last](Hasher& hasher) { + hasher.add_bytes(first, last); + return hasher.get_hash(); + }); + } - // Try shasum + std::string get_string_hash(StringView sv, Algorithm algo) noexcept + { + return get_bytes_hash(sv.data(), sv.data() + sv.size(), algo); + } + + // TODO: use Files::Filesystem to open a file + std::string get_file_hash(const Files::Filesystem&, + const fs::path& path, + Algorithm algo, + std::error_code& ec) noexcept + { + auto file = std::fstream(path.c_str(), std::ios_base::in | std::ios_base::binary); + if (!file) { - const auto ec_data = System::cmd_execute_and_capture_output( - Strings::format(R"(echo -n "%s" | shasum -a %s)", s, digest_size)); - if (ec_data.exit_code == 0) - { - return parse_shasum_output(ec_data.output); - } + ec.assign(ENOENT, std::system_category()); + return {}; } - Checks::exit_with_message(VCPKG_LINE_INFO, "Could not hash input string with %s", hash_type); + return do_hash(algo, [&file, &ec](Hasher& hasher) { + constexpr std::size_t buffer_size = 4096; + auto buffer = std::make_unique<char[]>(buffer_size); + for (;;) + { + file.read(buffer.get(), buffer_size); + if (file.eof()) + { + hasher.add_bytes(buffer.get(), buffer.get() + file.gcount()); + return hasher.get_hash(); + } + else if (file) + { + hasher.add_bytes(buffer.get(), buffer.get() + buffer_size); + } + else + { + ec = std::io_errc::stream; + return std::string(); + } + } + }); } -#endif } diff --git a/toolsrc/src/vcpkg/base/strings.cpp b/toolsrc/src/vcpkg/base/strings.cpp index 54a74a7a1..46e78a363 100644 --- a/toolsrc/src/vcpkg/base/strings.cpp +++ b/toolsrc/src/vcpkg/base/strings.cpp @@ -288,3 +288,43 @@ bool Strings::contains(StringView haystack, StringView needle) { return Strings::search(haystack, needle) != haystack.end(); } + +namespace vcpkg::Strings +{ + namespace + { + template<class Integral> + std::string b32_encode_implementation(Integral x) + { + static_assert(std::is_integral<Integral>::value, "b64url_encode must take an integer type"); + using Unsigned = std::make_unsigned_t<Integral>; + auto value = static_cast<Unsigned>(x); + + // 32 values, plus the implicit \0 + constexpr static char map[33] = "ABCDEFGHIJKLMNOP" + "QRSTUVWXYZ234567"; + + // log2(32) + constexpr static int shift = 5; + // 32 - 1 + constexpr static auto mask = 31; + + // ceiling(bitsize(Integral) / log2(32)) + constexpr static auto result_size = (sizeof(value) * 8 + shift - 1) / shift; + + std::string result; + result.reserve(result_size); + + for (std::size_t i = 0; i < result_size; ++i) + { + result.push_back(map[value & mask]); + value >>= shift; + } + + return result; + } + } + + std::string b32_encode(std::uint64_t x) noexcept { return b32_encode_implementation(x); } + +} diff --git a/toolsrc/src/vcpkg/base/stringview.cpp b/toolsrc/src/vcpkg/base/stringview.cpp index d0b2cd43a..6b159db48 100644 --- a/toolsrc/src/vcpkg/base/stringview.cpp +++ b/toolsrc/src/vcpkg/base/stringview.cpp @@ -76,8 +76,10 @@ namespace vcpkg std::string StringView::to_string() const { return std::string(m_ptr, m_size); } void StringView::to_string(std::string& s) const { s.append(m_ptr, m_size); } - bool StringView::operator==(StringView other) const + bool operator==(StringView lhs, StringView rhs) noexcept { - return other.size() == size() && memcmp(data(), other.data(), size()) == 0; + return lhs.size() == rhs.size() && memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; } + + bool operator!=(StringView lhs, StringView rhs) noexcept { return !(lhs == rhs); } } diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp index c5ff050f0..d9c6349be 100644 --- a/toolsrc/src/vcpkg/base/system.cpp +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -5,6 +5,7 @@ #include <vcpkg/base/system.debug.h> #include <vcpkg/base/system.h> #include <vcpkg/base/system.process.h> +#include <vcpkg/base/util.h> #include <ctime> @@ -188,12 +189,17 @@ namespace vcpkg } #if defined(_WIN32) - static std::wstring compute_clean_environment(const std::unordered_map<std::string, std::string>& extra_env) + static std::wstring compute_clean_environment(const std::unordered_map<std::string, std::string>& extra_env, + const std::string& prepend_to_path) { 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)"; - 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::string new_path = Strings::format(R"(Path=%s%s;%s;%s\Wbem;%s\WindowsPowerShell\v1.0\)", + prepend_to_path, + SYSTEM_32, + SYSTEM_ROOT, + SYSTEM_32, + SYSTEM_32); std::vector<std::wstring> env_wstrings = { L"ALLUSERSPROFILE", @@ -348,7 +354,8 @@ namespace vcpkg #endif int System::cmd_execute_clean(const ZStringView cmd_line, - const std::unordered_map<std::string, std::string>& extra_env) + const std::unordered_map<std::string, std::string>& extra_env, + const std::string& prepend_to_path) { auto timer = Chrono::ElapsedTimer::create_started(); #if defined(_WIN32) @@ -357,7 +364,7 @@ namespace vcpkg memset(&process_info, 0, sizeof(PROCESS_INFORMATION)); g_ctrl_c_state.transition_to_spawn_process(); - auto clean_env = compute_clean_environment(extra_env); + auto clean_env = compute_clean_environment(extra_env, prepend_to_path); windows_create_process(cmd_line, clean_env.data(), process_info, NULL); CloseHandle(process_info.hThread); @@ -375,6 +382,8 @@ namespace vcpkg "CreateProcessW() returned ", exit_code, " after ", static_cast<int>(timer.microseconds()), " us\n"); return static_cast<int>(exit_code); #else + // TODO: this should create a clean environment on Linux/macOS + Util::unused(extra_env, prepend_to_path); Debug::print("system(", cmd_line, ")\n"); fflush(nullptr); int rc = system(cmd_line.c_str()); @@ -543,10 +552,7 @@ namespace vcpkg return Strings::to_utf8(ret); } #else - Optional<std::string> System::get_registry_string(void* base_hkey, StringView sub_key, StringView valuename) - { - return nullopt; - } + Optional<std::string> System::get_registry_string(void*, StringView, StringView) { return nullopt; } #endif static const Optional<fs::path>& get_program_files() @@ -608,10 +614,7 @@ namespace vcpkg void System::register_console_ctrl_handler() {} #endif - int System::get_num_logical_cores() - { - return std::thread::hardware_concurrency(); - } + int System::get_num_logical_cores() { return std::thread::hardware_concurrency(); } } namespace vcpkg::Debug diff --git a/toolsrc/src/vcpkg/base/system.print.cpp b/toolsrc/src/vcpkg/base/system.print.cpp index c7c9981a7..fc8e4184c 100644 --- a/toolsrc/src/vcpkg/base/system.print.cpp +++ b/toolsrc/src/vcpkg/base/system.print.cpp @@ -1,6 +1,7 @@ #include "pch.h" #include <vcpkg/base/system.print.h> +#include <vcpkg/base/util.h> namespace vcpkg::System { @@ -21,6 +22,9 @@ namespace vcpkg::System System::print2(message); SetConsoleTextAttribute(console_handle, original_color); #else + // TODO: add color handling code + // it should probably use VT-220 codes + Util::unused(c); System::print2(message); #endif } diff --git a/toolsrc/src/vcpkg/binaryparagraph.cpp b/toolsrc/src/vcpkg/binaryparagraph.cpp index 4b80debab..8b1886098 100644 --- a/toolsrc/src/vcpkg/binaryparagraph.cpp +++ b/toolsrc/src/vcpkg/binaryparagraph.cpp @@ -27,7 +27,7 @@ namespace vcpkg BinaryParagraph::BinaryParagraph() = default; - BinaryParagraph::BinaryParagraph(std::unordered_map<std::string, std::string> fields) + BinaryParagraph::BinaryParagraph(Parse::RawParagraph fields) { using namespace vcpkg::Parse; diff --git a/toolsrc/src/vcpkg/build.cpp b/toolsrc/src/vcpkg/build.cpp index daff7e6c8..618e4126b 100644 --- a/toolsrc/src/vcpkg/build.cpp +++ b/toolsrc/src/vcpkg/build.cpp @@ -9,6 +9,7 @@ #include <vcpkg/base/system.debug.h>
#include <vcpkg/base/system.print.h>
#include <vcpkg/base/system.process.h>
+#include <vcpkg/base/util.h>
#include <vcpkg/build.h>
#include <vcpkg/commands.h>
@@ -23,6 +24,7 @@ #include <vcpkg/vcpkglib.h>
using vcpkg::Build::BuildResult;
+using vcpkg::Dependencies::PathsPortFileProvider;
using vcpkg::Parse::ParseControlErrorInfo;
using vcpkg::Parse::ParseExpected;
@@ -31,43 +33,27 @@ namespace vcpkg::Build::Command using Dependencies::InstallPlanAction;
using Dependencies::InstallPlanType;
- static constexpr StringLiteral OPTION_CHECKS_ONLY = "--checks-only";
-
void perform_and_exit_ex(const FullPackageSpec& full_spec,
- const fs::path& port_dir,
+ const SourceControlFileLocation& scfl,
const ParsedArguments& options,
const VcpkgPaths& paths)
{
- const PackageSpec& spec = full_spec.package_spec;
- if (Util::Sets::contains(options.switches, OPTION_CHECKS_ONLY))
- {
- const auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, spec.triplet());
- const auto build_info = Build::read_build_info(paths.get_filesystem(), paths.build_info_file_path(spec));
- const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info);
- Checks::check_exit(VCPKG_LINE_INFO, error_count == 0);
- Checks::exit_success(VCPKG_LINE_INFO);
- }
-
- const ParseExpected<SourceControlFile> source_control_file =
- Paragraphs::try_load_port(paths.get_filesystem(), port_dir);
+ vcpkg::Util::unused(options);
- if (!source_control_file.has_value())
- {
- print_error_message(source_control_file.error());
- Checks::exit_fail(VCPKG_LINE_INFO);
- }
+ const StatusParagraphs status_db = database_load_check(paths);
+ const PackageSpec& spec = full_spec.package_spec;
+ const SourceControlFile& scf = *scfl.source_control_file;
- const auto& scf = source_control_file.value_or_exit(VCPKG_LINE_INFO);
Checks::check_exit(VCPKG_LINE_INFO,
- spec.name() == scf->core_paragraph->name,
+ spec.name() == scf.core_paragraph->name,
"The Source field inside the CONTROL file does not match the port directory: '%s' != '%s'",
- scf->core_paragraph->name,
+ scf.core_paragraph->name,
spec.name());
- const StatusParagraphs status_db = database_load_check(paths);
const Build::BuildPackageOptions build_package_options{
Build::UseHeadVersion::NO,
Build::AllowDownloads::YES,
+ Build::OnlyDownloads::NO,
Build::CleanBuildtrees::NO,
Build::CleanPackages::NO,
Build::CleanDownloads::NO,
@@ -79,8 +65,7 @@ namespace vcpkg::Build::Command std::set<std::string> features_as_set(full_spec.features.begin(), full_spec.features.end());
features_as_set.emplace("core");
- const Build::BuildPackageConfig build_config{
- *scf, spec.triplet(), fs::path{port_dir}, build_package_options, features_as_set};
+ const Build::BuildPackageConfig build_config{scfl, spec.triplet(), build_package_options, features_as_set};
const auto build_timer = Chrono::ElapsedTimer::create_started();
const auto result = Build::build_package(paths, build_config, status_db);
@@ -111,15 +96,11 @@ namespace vcpkg::Build::Command Checks::exit_success(VCPKG_LINE_INFO);
}
- static constexpr std::array<CommandSwitch, 1> BUILD_SWITCHES = {{
- {OPTION_CHECKS_ONLY, "Only run checks, do not rebuild package"},
- }};
-
const CommandStructure COMMAND_STRUCTURE = {
Help::create_example_string("build zlib:x64-windows"),
1,
1,
- {BUILD_SWITCHES, {}},
+ {{}, {}},
nullptr,
};
@@ -128,10 +109,19 @@ namespace vcpkg::Build::Command // Build only takes a single package and all dependencies must already be installed
const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE);
std::string first_arg = args.command_arguments.at(0);
+
const FullPackageSpec spec = Input::check_and_get_full_package_spec(
std::move(first_arg), default_triplet, COMMAND_STRUCTURE.example_text);
+
Input::check_triplet(spec.package_spec.triplet(), paths);
- perform_and_exit_ex(spec, paths.port_dir(spec.package_spec), options, paths);
+
+ PathsPortFileProvider provider(paths, args.overlay_ports.get());
+ const auto port_name = spec.package_spec.name();
+ const auto* scfl = provider.get_control_file(port_name).get();
+
+ Checks::check_exit(VCPKG_LINE_INFO, scfl != nullptr, "Error: Couldn't find port '%s'", port_name);
+
+ perform_and_exit_ex(spec, *scfl, options, paths);
}
}
@@ -195,7 +185,7 @@ namespace vcpkg::Build static const std::string LIBRARY_LINKAGE = "LibraryLinkage";
}
- CStringView to_vcvarsall_target(const std::string& cmake_system_name)
+ static CStringView to_vcvarsall_target(const std::string& cmake_system_name)
{
if (cmake_system_name.empty()) return "";
if (cmake_system_name == "Windows") return "";
@@ -204,7 +194,7 @@ namespace vcpkg::Build Checks::exit_with_message(VCPKG_LINE_INFO, "Unsupported vcvarsall target %s", cmake_system_name);
}
- CStringView to_vcvarsall_toolchain(const std::string& target_architecture, const Toolset& toolset)
+ static CStringView to_vcvarsall_toolchain(const std::string& target_architecture, const Toolset& toolset)
{
auto maybe_target_arch = System::to_cpu_architecture(target_architecture);
Checks::check_exit(
@@ -228,6 +218,23 @@ namespace vcpkg::Build }));
}
+ static auto make_env_passthrough(const PreBuildInfo& pre_build_info) -> std::unordered_map<std::string, std::string>
+ {
+ std::unordered_map<std::string, std::string> env;
+
+ for (auto&& env_var : pre_build_info.passthrough_env_vars)
+ {
+ auto env_val = System::get_environment_variable(env_var);
+
+ if (env_val)
+ {
+ env[env_var] = env_val.value_or_exit(VCPKG_LINE_INFO);
+ }
+ }
+
+ return env;
+ }
+
std::string make_build_env_cmd(const PreBuildInfo& pre_build_info, const Toolset& toolset)
{
if (pre_build_info.external_toolchain_file.has_value()) return "";
@@ -268,11 +275,12 @@ namespace vcpkg::Build {
bpgh.version = *p_ver;
}
+
bcf->core_paragraph = std::move(bpgh);
return bcf;
}
- static void write_binary_control_file(const VcpkgPaths& paths, BinaryControlFile bcf)
+ static void write_binary_control_file(const VcpkgPaths& paths, const BinaryControlFile& bcf)
{
std::string start = Strings::serialize(bcf.core_paragraph);
for (auto&& feature : bcf.features)
@@ -283,23 +291,37 @@ namespace vcpkg::Build paths.get_filesystem().write_contents(binary_control_file, start, VCPKG_LINE_INFO);
}
+ static std::vector<Features> get_dependencies(const SourceControlFile& scf,
+ const std::set<std::string>& feature_list,
+ const Triplet& triplet)
+ {
+ return Util::fmap_flatten(feature_list, [&](std::string const& feature) -> std::vector<Features> {
+ if (feature == "core")
+ {
+ return filter_dependencies_to_features(scf.core_paragraph->depends, triplet);
+ }
+
+ auto maybe_feature = scf.find_feature(feature);
+ Checks::check_exit(VCPKG_LINE_INFO, maybe_feature.has_value());
+
+ return filter_dependencies_to_features(maybe_feature.get()->depends, triplet);
+ });
+ }
+
+ static std::vector<std::string> get_dependency_names(const SourceControlFile& scf,
+ const std::set<std::string>& feature_list,
+ const Triplet& triplet)
+ {
+ return Util::sort_unique_erase(
+ Util::fmap(get_dependencies(scf, feature_list, triplet), [&](const Features& feat) { return feat.name; }));
+ }
+
static std::vector<FeatureSpec> compute_required_feature_specs(const BuildPackageConfig& config,
const StatusParagraphs& status_db)
{
const Triplet& triplet = config.triplet;
- const std::vector<std::string> dep_strings =
- 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 maybe_feature = config.scf.find_feature(feature);
- Checks::check_exit(VCPKG_LINE_INFO, maybe_feature.has_value());
-
- return filter_dependencies(maybe_feature.get()->depends, triplet);
- });
+ const std::vector<std::string> dep_strings = get_dependency_names(config.scf, config.feature_list, triplet);
auto dep_fspecs = FeatureSpec::from_strings_and_triplet(dep_strings, triplet);
Util::sort_unique_erase(dep_fspecs);
@@ -351,22 +373,17 @@ namespace vcpkg::Build return concurrency;
}
- static ExtendedBuildResult do_build_package(const VcpkgPaths& paths,
- const PreBuildInfo& pre_build_info,
- const PackageSpec& spec,
- const std::string& abi_tag,
- const BuildPackageConfig& config)
+ static std::vector<System::CMakeVariable> get_cmake_vars(const VcpkgPaths& paths,
+ const BuildPackageConfig& config,
+ const Triplet& triplet,
+ const Toolset& toolset)
{
- auto& fs = paths.get_filesystem();
- const Triplet& triplet = spec.triplet();
-
#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
- const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE);
const fs::path& git_exe_path = paths.get_tool_exe(Tools::GIT);
std::string all_features;
@@ -375,13 +392,13 @@ namespace vcpkg::Build all_features.append(feature->name + ";");
}
- const Toolset& toolset = paths.get_toolset(pre_build_info);
-
std::vector<System::CMakeVariable> variables{
{"CMD", "BUILD"},
{"PORT", config.scf.core_paragraph->name},
{"CURRENT_PORT_DIR", config.port_dir},
- {"TARGET_TRIPLET", spec.triplet().canonical_name()},
+ {"VCPKG_ROOT_PATH", paths.root},
+ {"TARGET_TRIPLET", triplet.canonical_name()},
+ {"TARGET_TRIPLET_FILE", paths.get_triplet_file_path(triplet).u8string()},
{"VCPKG_PLATFORM_TOOLSET", toolset.version.c_str()},
{"VCPKG_USE_HEAD_VERSION", Util::Enum::to_bool(config.build_package_options.use_head_version) ? "1" : "0"},
{"DOWNLOADS", paths.downloads},
@@ -392,14 +409,58 @@ namespace vcpkg::Build {"VCPKG_CONCURRENCY", std::to_string(get_concurrency())},
};
+ if (Util::Enum::to_bool(config.build_package_options.only_downloads))
+ {
+ variables.push_back({"VCPKG_DOWNLOAD_MODE", "true"});
+ }
+
if (!System::get_environment_variable("VCPKG_FORCE_SYSTEM_BINARIES").has_value())
{
variables.push_back({"GIT", git_exe_path});
}
+ const Files::Filesystem& fs = paths.get_filesystem();
+ if (fs.is_regular_file(config.port_dir / "environment-overrides.cmake"))
+ {
+ variables.emplace_back("VCPKG_ENV_OVERRIDES_FILE", config.port_dir / "environment-overrides.cmake");
+ }
+
+ std::vector<FeatureSpec> dependencies =
+ filter_dependencies_to_specs(config.scfl.source_control_file->core_paragraph->depends, triplet);
+
+ std::vector<std::string> port_toolchains;
+ for (const FeatureSpec& dependency : dependencies)
+ {
+ const fs::path port_toolchain_path = paths.installed / dependency.triplet().canonical_name() / "share" /
+ dependency.spec().name() / "port-toolchain.cmake";
+
+ if (fs.is_regular_file(port_toolchain_path))
+ {
+ System::print2(port_toolchain_path.u8string());
+ port_toolchains.emplace_back(port_toolchain_path.u8string());
+ }
+ }
+
+ if (!port_toolchains.empty())
+ {
+ variables.emplace_back("VCPKG_PORT_TOOLCHAINS", Strings::join(";", port_toolchains));
+ }
+
+ return variables;
+ }
+
+ static std::string make_build_cmd(const VcpkgPaths& paths,
+ const PreBuildInfo& pre_build_info,
+ const BuildPackageConfig& config,
+ const Triplet& triplet)
+ {
+ const Toolset& toolset = paths.get_toolset(pre_build_info);
+ const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE);
+ std::vector<System::CMakeVariable> variables = get_cmake_vars(paths, config, triplet, toolset);
+
const std::string cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path, paths.ports_cmake, variables);
- auto command = make_build_env_cmd(pre_build_info, toolset);
+ std::string command = make_build_env_cmd(pre_build_info, toolset);
if (!command.empty())
{
#ifdef _WIN32
@@ -408,10 +469,112 @@ namespace vcpkg::Build command.append(" && ");
#endif
}
+
command.append(cmd_launch_cmake);
+
+ return command;
+ }
+
+ static std::string get_triplet_abi(const VcpkgPaths& paths,
+ const PreBuildInfo& pre_build_info,
+ const Triplet& triplet)
+ {
+ static std::map<fs::path, std::string> s_hash_cache;
+
+ const fs::path triplet_file_path = paths.get_triplet_file_path(triplet);
+ const auto& fs = paths.get_filesystem();
+
+ std::string hash;
+
+ auto it_hash = s_hash_cache.find(triplet_file_path);
+ if (it_hash != s_hash_cache.end())
+ {
+ hash = it_hash->second;
+ }
+ else
+ {
+ const auto algo = Hash::Algorithm::Sha1;
+ hash = Hash::get_file_hash(VCPKG_LINE_INFO, fs, triplet_file_path, algo);
+
+ if (auto p = pre_build_info.external_toolchain_file.get())
+ {
+ hash += "-";
+ hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, *p, algo);
+ }
+ else if (pre_build_info.cmake_system_name == "Linux")
+ {
+ hash += "-";
+ hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "linux.cmake", algo);
+ }
+ else if (pre_build_info.cmake_system_name == "Darwin")
+ {
+ hash += "-";
+ hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "osx.cmake", algo);
+ }
+ else if (pre_build_info.cmake_system_name == "FreeBSD")
+ {
+ hash += "-";
+ hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "freebsd.cmake", algo);
+ }
+ else if (pre_build_info.cmake_system_name == "Android")
+ {
+ hash += "-";
+ hash += Hash::get_file_hash(VCPKG_LINE_INFO, fs, paths.scripts / "toolchains" / "android.cmake", algo);
+ }
+
+ s_hash_cache.emplace(triplet_file_path, hash);
+ }
+
+ return hash;
+ }
+
+ static ExtendedBuildResult do_build_package(const VcpkgPaths& paths,
+ const PreBuildInfo& pre_build_info,
+ const PackageSpec& spec,
+ const std::string& abi_tag,
+ const BuildPackageConfig& config)
+ {
+ auto& fs = paths.get_filesystem();
+
+#if defined(_WIN32)
+ const fs::path& powershell_exe_path = paths.get_tool_exe("powershell-core");
+ if (!fs.exists(powershell_exe_path.parent_path() / "powershell.exe"))
+ {
+ fs.copy(powershell_exe_path, powershell_exe_path.parent_path() / "powershell.exe", fs::copy_options::none);
+ }
+#endif
+
+ const Triplet& triplet = spec.triplet();
+ const auto& triplet_file_path = paths.get_triplet_file_path(spec.triplet()).u8string();
+
+ if (!Strings::case_insensitive_ascii_starts_with(triplet_file_path, paths.triplets.u8string()))
+ {
+ System::printf("-- Loading triplet configuration from: %s\n", triplet_file_path);
+ }
+ if (!Strings::case_insensitive_ascii_starts_with(config.port_dir.u8string(), paths.ports.u8string()))
+ {
+ System::printf("-- Installing port from location: %s\n", config.port_dir.u8string());
+ }
+
const auto timer = Chrono::ElapsedTimer::create_started();
- const int return_code = System::cmd_execute_clean(command);
+ std::string command = make_build_cmd(paths, pre_build_info, config, triplet);
+ std::unordered_map<std::string, std::string> env = make_env_passthrough(pre_build_info);
+
+#if defined(_WIN32)
+ const int return_code =
+ System::cmd_execute_clean(command, env, powershell_exe_path.parent_path().u8string() + ";");
+#else
+ const int return_code = System::cmd_execute_clean(command, env);
+#endif
+ // With the exception of empty packages, builds in "Download Mode" always result in failure.
+ if (config.build_package_options.only_downloads == Build::OnlyDownloads::YES)
+ {
+ // TODO: Capture executed command output and evaluate whether the failure was intended.
+ // If an unintended error occurs then return a BuildResult::DOWNLOAD_FAILURE status.
+ return BuildResult::DOWNLOADED;
+ }
+
const auto buildtimeus = timer.microseconds();
const auto spec_string = spec.to_string();
@@ -428,9 +591,11 @@ namespace vcpkg::Build }
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 size_t error_count =
+ PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info, config.port_dir);
- auto bcf = create_binary_control_file(*config.scf.core_paragraph, triplet, build_info, abi_tag);
+ std::unique_ptr<BinaryControlFile> bcf =
+ create_binary_control_file(*config.scf.core_paragraph, triplet, build_info, abi_tag);
if (error_count != 0)
{
@@ -468,7 +633,8 @@ namespace vcpkg::Build if (fs.is_directory(file)) // Will only keep the logs
{
std::error_code ec;
- fs.remove_all(file, ec);
+ fs::path failure_point;
+ fs.remove_all(file, ec, failure_point);
}
}
}
@@ -481,15 +647,15 @@ namespace vcpkg::Build const PreBuildInfo& pre_build_info,
Span<const AbiEntry> dependency_abis)
{
- if (config.build_package_options.binary_caching == BinaryCaching::NO) 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(dependency_abis.begin(), dependency_abis.end());
- abi_tag_entries.emplace_back(AbiEntry{"cmake", paths.get_tool_version(Tools::CMAKE)});
+ // Sorted here as the order of dependency_abis is the only
+ // non-deterministicly ordered set of AbiEntries
+ Util::sort(abi_tag_entries);
// If there is an unusually large number of files in the port then
// something suspicious is going on. Rather than hash all of them
@@ -497,15 +663,18 @@ namespace vcpkg::Build const int max_port_file_count = 100;
// the order of recursive_directory_iterator is undefined so save the names to sort
- std::vector<fs::path> port_files;
+ std::vector<AbiEntry> port_files;
for (auto& port_file : fs::stdfs::recursive_directory_iterator(config.port_dir))
{
- if (fs::is_regular_file(status(port_file)))
+ if (fs::is_regular_file(fs.status(VCPKG_LINE_INFO, port_file)))
{
- port_files.push_back(port_file);
+ port_files.emplace_back(
+ port_file.path().filename().u8string(),
+ vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO, fs, port_file, Hash::Algorithm::Sha1));
+
if (port_files.size() > max_port_file_count)
{
- abi_tag_entries.emplace_back(AbiEntry{"no_hash_max_portfile", ""});
+ abi_tag_entries.emplace_back("no_hash_max_portfile", "");
break;
}
}
@@ -513,36 +682,39 @@ namespace vcpkg::Build if (port_files.size() <= max_port_file_count)
{
- std::sort(port_files.begin(), port_files.end());
+ Util::sort(port_files, [](const AbiEntry& l, const AbiEntry& r) {
+ return l.value < r.value || (l.value == r.value && l.key < r.key);
+ });
- int counter = 0;
- for (auto& port_file : port_files)
- {
- // When vcpkg takes a dependency on C++17 it can use fs::relative,
- // which will give a stable ordering and better names in the key entry.
- // this is not available in the filesystem TS so instead number the files for the key.
- std::string key = Strings::format("file_%03d", counter++);
- if (Debug::g_debugging)
- {
- System::print2("[DEBUG] mapping ", key, " from ", port_file.u8string(), "\n");
- }
- abi_tag_entries.emplace_back(AbiEntry{key, vcpkg::Hash::get_file_hash(fs, port_file, "SHA1")});
- }
+ std::move(port_files.begin(), port_files.end(), std::back_inserter(abi_tag_entries));
}
- abi_tag_entries.emplace_back(AbiEntry{
+ abi_tag_entries.emplace_back("cmake", paths.get_tool_version(Tools::CMAKE));
+
+#if defined(_WIN32)
+ abi_tag_entries.emplace_back("powershell", paths.get_tool_version("powershell-core"));
+#endif
+
+ abi_tag_entries.emplace_back(
"vcpkg_fixup_cmake_targets",
- vcpkg::Hash::get_file_hash(fs, paths.scripts / "cmake" / "vcpkg_fixup_cmake_targets.cmake", "SHA1")});
+ vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO,
+ fs,
+ paths.scripts / "cmake" / "vcpkg_fixup_cmake_targets.cmake",
+ Hash::Algorithm::Sha1));
- abi_tag_entries.emplace_back(AbiEntry{"triplet", pre_build_info.triplet_abi_tag});
+ abi_tag_entries.emplace_back("triplet", pre_build_info.triplet_abi_tag);
+ abi_tag_entries.emplace_back("features", Strings::join(";", config.feature_list));
- const std::string features = Strings::join(";", config.feature_list);
- abi_tag_entries.emplace_back(AbiEntry{"features", features});
+ if (pre_build_info.public_abi_override)
+ {
+ abi_tag_entries.emplace_back(
+ "public_abi_override",
+ Hash::get_string_hash(pre_build_info.public_abi_override.value_or_exit(VCPKG_LINE_INFO),
+ Hash::Algorithm::Sha1));
+ }
if (config.build_package_options.use_head_version == UseHeadVersion::YES)
- abi_tag_entries.emplace_back(AbiEntry{"head", ""});
-
- Util::sort(abi_tag_entries);
+ abi_tag_entries.emplace_back("head", "");
const std::string full_abi_info =
Strings::join("", abi_tag_entries, [](const AbiEntry& p) { return p.key + " " + p.value + "\n"; });
@@ -567,24 +739,25 @@ namespace vcpkg::Build const auto abi_file_path = paths.buildtrees / name / (triplet.canonical_name() + ".vcpkg_abi_info.txt");
fs.write_contents(abi_file_path, full_abi_info, VCPKG_LINE_INFO);
- return AbiTagAndFile{Hash::get_file_hash(fs, abi_file_path, "SHA1"), abi_file_path};
+ return AbiTagAndFile{Hash::get_file_hash(VCPKG_LINE_INFO, fs, abi_file_path, Hash::Algorithm::Sha1),
+ abi_file_path};
}
System::print2(
- "Warning: binary caching disabled because abi keys are missing values:\n",
+ "Warning: abi keys are missing values:\n",
Strings::join("", abi_tag_entries_missing, [](const AbiEntry& e) { return " " + e.key + "\n"; }),
"\n");
return nullopt;
}
- static void decompress_archive(const VcpkgPaths& paths, const PackageSpec& spec, const fs::path& archive_path)
+ static int 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);
+ fs.remove_all(pkg_path, VCPKG_LINE_INFO);
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());
@@ -592,12 +765,13 @@ namespace vcpkg::Build #if defined(_WIN32)
auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP);
- System::cmd_execute_clean(Strings::format(
+ int result = 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(
+ int result = System::cmd_execute_clean(
Strings::format(R"(unzip -qq "%s" "-d%s")", archive_path.u8string(), pkg_path.u8string()));
#endif
+ return result;
}
// Compress the source directory into the destination file.
@@ -641,13 +815,17 @@ namespace vcpkg::Build Util::sort_unique_erase(dep_pspecs);
// Find all features that aren't installed. This mutates required_fspecs.
- Util::erase_remove_if(required_fspecs, [&](FeatureSpec const& fspec) {
- return status_db.is_installed(fspec) || fspec.name() == name;
- });
-
- if (!required_fspecs.empty())
+ // Skip this validation when running in Download Mode.
+ if (config.build_package_options.only_downloads != Build::OnlyDownloads::YES)
{
- return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(required_fspecs)};
+ Util::erase_remove_if(required_fspecs, [&](FeatureSpec const& fspec) {
+ return status_db.is_installed(fspec) || fspec.name() == name;
+ });
+
+ if (!required_fspecs.empty())
+ {
+ return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(required_fspecs)};
+ }
}
const PackageSpec spec =
@@ -658,36 +836,51 @@ namespace vcpkg::Build // dep_pspecs was not destroyed
for (auto&& pspec : dep_pspecs)
{
- if (pspec == spec) continue;
+ if (pspec == spec || Util::Enum::to_bool(config.build_package_options.only_downloads))
+ {
+ 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 auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet);
+ const auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet, config.scfl);
auto maybe_abi_tag_and_file = compute_abi_tag(paths, config, pre_build_info, dependency_abis);
+ if (!maybe_abi_tag_and_file)
+ {
+ return do_build_package_and_clean_buildtrees(
+ paths, pre_build_info, spec, pre_build_info.public_abi_override.value_or(AbiTagAndFile{}.tag), config);
+ }
+ std::error_code ec;
const auto abi_tag_and_file = maybe_abi_tag_and_file.get();
-
- if (config.build_package_options.binary_caching == BinaryCaching::YES && 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;
+ const fs::path abi_package_dir = paths.package_dir(spec) / "share" / spec.name();
+ const fs::path abi_file_in_package = paths.package_dir(spec) / "share" / spec.name() / "vcpkg_abi_info.txt";
+
+ if (config.build_package_options.binary_caching == BinaryCaching::YES)
{
- 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))
{
System::print2("Using cached binary package: ", archive_path.u8string(), "\n");
- decompress_archive(paths, spec, archive_path);
+ int archive_result = decompress_archive(paths, spec, archive_path);
+
+ if (archive_result != 0)
+ {
+ System::print2("Failed to decompress archive package\n");
+ return BuildResult::BUILD_FAILED;
+ }
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));
+ auto bcf = std::make_unique<BinaryControlFile>(std::move(maybe_bcf).value_or_exit(VCPKG_LINE_INFO));
return {BuildResult::SUCCEEDED, std::move(bcf)};
}
@@ -705,71 +898,68 @@ namespace vcpkg::Build }
}
- System::print2("Could not locate cached archive: ", archive_path.u8string(), "\n");
+ System::printf("Could not locate cached archive: %s\n", archive_path.u8string());
+ }
- ExtendedBuildResult result = do_build_package_and_clean_buildtrees(
- paths, pre_build_info, spec, maybe_abi_tag_and_file.value_or(AbiTagAndFile{}).tag, config);
+ ExtendedBuildResult result = do_build_package_and_clean_buildtrees(
+ paths, pre_build_info, spec, pre_build_info.public_abi_override.value_or(abi_tag_and_file->tag), config);
- 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());
+ fs.create_directories(abi_package_dir, ec);
+ Checks::check_exit(VCPKG_LINE_INFO, !ec, "Coud not create directory %s", abi_package_dir.u8string());
+ 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());
- if (result.code == BuildResult::SUCCEEDED)
- {
- const auto tmp_archive_path = paths.buildtrees / spec.name() / (spec.triplet().to_string() + ".zip");
+ if (config.build_package_options.binary_caching == BinaryCaching::YES && result.code == BuildResult::SUCCEEDED)
+ {
+ const auto tmp_archive_path = paths.buildtrees / spec.name() / (spec.triplet().to_string() + ".zip");
- compress_archive(paths, spec, tmp_archive_path);
+ compress_archive(paths, spec, tmp_archive_path);
- fs.create_directories(archive_path.parent_path(), ec);
- fs.rename_or_copy(tmp_archive_path, archive_path, ".tmp", ec);
- if (ec)
- {
- System::printf(System::Color::warning,
- "Failed to store binary cache %s: %s\n",
- archive_path.u8string(),
- ec.message());
- }
- else
- System::printf("Stored binary cache: %s\n", archive_path.u8string());
+ fs.create_directories(archive_path.parent_path(), ec);
+ fs.rename_or_copy(tmp_archive_path, archive_path, ".tmp", ec);
+ if (ec)
+ {
+ System::printf(System::Color::warning,
+ "Failed to store binary cache %s: %s\n",
+ archive_path.u8string(),
+ ec.message());
}
- else if (result.code == BuildResult::BUILD_FAILED || result.code == BuildResult::POST_BUILD_CHECKS_FAILED)
+ else
+ System::printf("Stored binary cache: %s\n", archive_path.u8string());
+ }
+ else if (config.build_package_options.binary_caching == BinaryCaching::YES &&
+ (result.code == BuildResult::BUILD_FAILED || result.code == BuildResult::POST_BUILD_CHECKS_FAILED))
+ {
+ if (!fs.exists(archive_tombstone_path))
{
- if (!fs.exists(archive_tombstone_path))
- {
- // Build failed, store all failure logs in the tombstone.
- const auto tmp_log_path = paths.buildtrees / spec.name() / "tmp_failure_logs";
- const auto tmp_log_path_destination = tmp_log_path / spec.name();
- const auto tmp_failure_zip = paths.buildtrees / spec.name() / "failure_logs.zip";
- fs.create_directories(tmp_log_path_destination, ec);
+ // Build failed, store all failure logs in the tombstone.
+ const auto tmp_log_path = paths.buildtrees / spec.name() / "tmp_failure_logs";
+ const auto tmp_log_path_destination = tmp_log_path / spec.name();
+ const auto tmp_failure_zip = paths.buildtrees / spec.name() / "failure_logs.zip";
+ fs.create_directories(tmp_log_path_destination, ec);
- for (auto& log_file : fs::stdfs::directory_iterator(paths.buildtrees / spec.name()))
+ for (auto& log_file : fs::stdfs::directory_iterator(paths.buildtrees / spec.name()))
+ {
+ if (log_file.path().extension() == ".log")
{
- if (log_file.path().extension() == ".log")
- {
- fs.copy_file(log_file.path(),
- tmp_log_path_destination / log_file.path().filename(),
- fs::stdfs::copy_options::none,
- ec);
- }
+ fs.copy_file(log_file.path(),
+ tmp_log_path_destination / log_file.path().filename(),
+ fs::stdfs::copy_options::none,
+ ec);
}
+ }
- compress_directory(paths, tmp_log_path, paths.buildtrees / spec.name() / "failure_logs.zip");
+ compress_directory(paths, tmp_log_path, paths.buildtrees / spec.name() / "failure_logs.zip");
- fs.create_directories(archive_tombstone_path.parent_path(), ec);
- fs.rename_or_copy(tmp_failure_zip, archive_tombstone_path, ".tmp", ec);
+ fs.create_directories(archive_tombstone_path.parent_path(), ec);
+ fs.rename_or_copy(tmp_failure_zip, archive_tombstone_path, ".tmp", ec);
- // clean up temporary directory
- fs.remove_all(tmp_log_path, ec);
- }
+ // clean up temporary directory
+ fs.remove_all(tmp_log_path, VCPKG_LINE_INFO);
}
-
- return result;
}
- return do_build_package_and_clean_buildtrees(
- paths, pre_build_info, spec, maybe_abi_tag_and_file.value_or(AbiTagAndFile{}).tag, config);
+ return result;
}
const std::string& to_string(const BuildResult build_result)
@@ -781,6 +971,7 @@ namespace vcpkg::Build static const std::string POST_BUILD_CHECKS_FAILED_STRING = "POST_BUILD_CHECKS_FAILED";
static const std::string CASCADED_DUE_TO_MISSING_DEPENDENCIES_STRING = "CASCADED_DUE_TO_MISSING_DEPENDENCIES";
static const std::string EXCLUDED_STRING = "EXCLUDED";
+ static const std::string DOWNLOADED_STRING = "DOWNLOADED";
switch (build_result)
{
@@ -791,6 +982,7 @@ namespace vcpkg::Build case BuildResult::FILE_CONFLICTS: return FILE_CONFLICTS_STRING;
case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: return CASCADED_DUE_TO_MISSING_DEPENDENCIES_STRING;
case BuildResult::EXCLUDED: return EXCLUDED_STRING;
+ case BuildResult::DOWNLOADED: return DOWNLOADED_STRING;
default: Checks::unreachable(VCPKG_LINE_INFO);
}
}
@@ -812,7 +1004,7 @@ namespace vcpkg::Build Commands::Version::version());
}
- static BuildInfo inner_create_buildinfo(std::unordered_map<std::string, std::string> pgh)
+ static BuildInfo inner_create_buildinfo(Parse::RawParagraph pgh)
{
Parse::ParagraphParser parser(std::move(pgh));
@@ -869,25 +1061,35 @@ namespace vcpkg::Build BuildInfo read_build_info(const Files::Filesystem& fs, const fs::path& filepath)
{
- const Expected<std::unordered_map<std::string, std::string>> pghs =
- Paragraphs::get_single_paragraph(fs, filepath);
+ const Expected<Parse::RawParagraph> pghs = Paragraphs::get_single_paragraph(fs, filepath);
Checks::check_exit(VCPKG_LINE_INFO, pghs.get() != nullptr, "Invalid BUILD_INFO file for package");
return inner_create_buildinfo(*pghs.get());
}
- PreBuildInfo PreBuildInfo::from_triplet_file(const VcpkgPaths& paths, const Triplet& triplet)
+ PreBuildInfo PreBuildInfo::from_triplet_file(const VcpkgPaths& paths,
+ const Triplet& triplet,
+ Optional<const SourceControlFileLocation&> port)
{
static constexpr CStringView FLAG_GUID = "c35112b6-d1ba-415b-aa5d-81de856ef8eb";
const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE);
const fs::path ports_cmake_script_path = paths.scripts / "get_triplet_environment.cmake";
- const fs::path triplet_file_path = paths.triplets / (triplet.canonical_name() + ".cmake");
+ const fs::path triplet_file_path = paths.get_triplet_file_path(triplet);
+
+ std::vector<System::CMakeVariable> args{{"CMAKE_TRIPLET_FILE", triplet_file_path}};
+
+ if (port)
+ {
+ const SourceControlFileLocation& scfl = port.value_or_exit(VCPKG_LINE_INFO);
+
+ if (paths.get_filesystem().is_regular_file(scfl.source_location / "environment-overrides.cmake"))
+ {
+ args.emplace_back("VCPKG_ENV_OVERRIDES_FILE", scfl.source_location / "environment-overrides.cmake");
+ }
+ }
+
+ const auto cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path, ports_cmake_script_path, args);
- const auto cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path,
- ports_cmake_script_path,
- {
- {"CMAKE_TRIPLET_FILE", triplet_file_path},
- });
const auto ec_data = System::cmd_execute_and_capture_output(cmd_launch_cmake);
Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, ec_data.output);
@@ -895,6 +1097,8 @@ namespace vcpkg::Build PreBuildInfo pre_build_info;
+ pre_build_info.port = port;
+
const auto e = lines.cend();
auto cur = std::find(lines.cbegin(), e, FLAG_GUID);
if (cur != e) ++cur;
@@ -913,105 +1117,61 @@ namespace vcpkg::Build const std::string variable_name = s.at(0);
const std::string variable_value = variable_with_no_value ? "" : s.at(1);
- if (variable_name == "VCPKG_TARGET_ARCHITECTURE")
- {
- pre_build_info.target_architecture = variable_value;
- continue;
- }
-
- if (variable_name == "VCPKG_CMAKE_SYSTEM_NAME")
- {
- pre_build_info.cmake_system_name = variable_value;
- continue;
- }
-
- if (variable_name == "VCPKG_CMAKE_SYSTEM_VERSION")
+ auto maybe_option = VCPKG_OPTIONS.find(variable_name);
+ if (maybe_option != VCPKG_OPTIONS.end())
{
- pre_build_info.cmake_system_version = variable_value;
- continue;
- }
-
- if (variable_name == "VCPKG_PLATFORM_TOOLSET")
- {
- pre_build_info.platform_toolset =
- variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
- continue;
- }
-
- if (variable_name == "VCPKG_VISUAL_STUDIO_PATH")
- {
- pre_build_info.visual_studio_path =
- variable_value.empty() ? nullopt : Optional<fs::path>{variable_value};
- continue;
- }
-
- if (variable_name == "VCPKG_CHAINLOAD_TOOLCHAIN_FILE")
- {
- pre_build_info.external_toolchain_file =
- variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
- continue;
+ switch (maybe_option->second)
+ {
+ case VcpkgTripletVar::TARGET_ARCHITECTURE:
+ pre_build_info.target_architecture = variable_value;
+ break;
+ case VcpkgTripletVar::CMAKE_SYSTEM_NAME: pre_build_info.cmake_system_name = variable_value; break;
+ case VcpkgTripletVar::CMAKE_SYSTEM_VERSION:
+ pre_build_info.cmake_system_version = variable_value;
+ break;
+ case VcpkgTripletVar::PLATFORM_TOOLSET:
+ pre_build_info.platform_toolset =
+ variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
+ break;
+ case VcpkgTripletVar::VISUAL_STUDIO_PATH:
+ pre_build_info.visual_studio_path =
+ variable_value.empty() ? nullopt : Optional<fs::path>{variable_value};
+ break;
+ case VcpkgTripletVar::CHAINLOAD_TOOLCHAIN_FILE:
+ pre_build_info.external_toolchain_file =
+ variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
+ break;
+ case VcpkgTripletVar::BUILD_TYPE:
+ if (variable_value.empty())
+ pre_build_info.build_type = nullopt;
+ else if (Strings::case_insensitive_ascii_equals(variable_value, "debug"))
+ pre_build_info.build_type = ConfigurationType::DEBUG;
+ else if (Strings::case_insensitive_ascii_equals(variable_value, "release"))
+ pre_build_info.build_type = ConfigurationType::RELEASE;
+ else
+ Checks::exit_with_message(
+ VCPKG_LINE_INFO, "Unknown setting for VCPKG_BUILD_TYPE: %s", variable_value);
+ break;
+ case VcpkgTripletVar::ENV_PASSTHROUGH:
+ pre_build_info.passthrough_env_vars = Strings::split(variable_value, ";");
+ break;
+ case VcpkgTripletVar::PUBLIC_ABI_OVERRIDE:
+ pre_build_info.public_abi_override =
+ variable_value.empty() ? nullopt : Optional<std::string>{variable_value};
+ break;
+ }
}
-
- if (variable_name == "VCPKG_BUILD_TYPE")
+ else
{
- if (variable_value.empty())
- pre_build_info.build_type = nullopt;
- else if (Strings::case_insensitive_ascii_equals(variable_value, "debug"))
- pre_build_info.build_type = ConfigurationType::DEBUG;
- else if (Strings::case_insensitive_ascii_equals(variable_value, "release"))
- pre_build_info.build_type = ConfigurationType::RELEASE;
- else
- Checks::exit_with_message(
- VCPKG_LINE_INFO, "Unknown setting for VCPKG_BUILD_TYPE: %s", variable_value);
- continue;
+ Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown variable name %s", line);
}
-
- Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown variable name %s", line);
}
- pre_build_info.triplet_abi_tag = [&]() {
- const auto& fs = paths.get_filesystem();
- static std::map<fs::path, std::string> s_hash_cache;
-
- auto it_hash = s_hash_cache.find(triplet_file_path);
- if (it_hash != s_hash_cache.end())
- {
- return it_hash->second;
- }
- auto hash = Hash::get_file_hash(fs, triplet_file_path, "SHA1");
-
- if (auto p = pre_build_info.external_toolchain_file.get())
- {
- hash += "-";
- hash += Hash::get_file_hash(fs, *p, "SHA1");
- }
- else if (pre_build_info.cmake_system_name == "Linux")
- {
- hash += "-";
- hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "linux.cmake", "SHA1");
- }
- else if (pre_build_info.cmake_system_name == "Darwin")
- {
- hash += "-";
- hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "osx.cmake", "SHA1");
- }
- else if (pre_build_info.cmake_system_name == "FreeBSD")
- {
- hash += "-";
- hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "freebsd.cmake", "SHA1");
- }
- else if (pre_build_info.cmake_system_name == "Android")
- {
- hash += "-";
- hash += Hash::get_file_hash(fs, paths.scripts / "toolchains" / "android.cmake", "SHA1");
- }
-
- s_hash_cache.emplace(triplet_file_path, hash);
- return hash;
- }();
+ pre_build_info.triplet_abi_tag = get_triplet_abi(paths, pre_build_info, triplet);
return pre_build_info;
}
+
ExtendedBuildResult::ExtendedBuildResult(BuildResult code) : code(code) {}
ExtendedBuildResult::ExtendedBuildResult(BuildResult code, std::unique_ptr<BinaryControlFile>&& bcf)
: code(code), binary_control_file(std::move(bcf))
diff --git a/toolsrc/src/vcpkg/commands.autocomplete.cpp b/toolsrc/src/vcpkg/commands.autocomplete.cpp index afef518eb..8449b7096 100644 --- a/toolsrc/src/vcpkg/commands.autocomplete.cpp +++ b/toolsrc/src/vcpkg/commands.autocomplete.cpp @@ -19,8 +19,8 @@ namespace vcpkg::Commands::Autocomplete Checks::exit_success(line_info); } - std::vector<std::string> combine_port_with_triplets(const std::string& port, - const std::vector<std::string>& triplets) + static std::vector<std::string> combine_port_with_triplets(const std::string& port, + const std::vector<std::string>& triplets) { return Util::fmap(triplets, [&](const std::string& triplet) { return Strings::format("%s:%s", port, triplet); }); @@ -90,7 +90,8 @@ namespace vcpkg::Commands::Autocomplete const auto port_name = match[2].str(); const auto triplet_prefix = match[3].str(); - auto maybe_port = Paragraphs::try_load_port(paths.get_filesystem(), paths.port_dir(port_name)); + // TODO: Support autocomplete for ports in --overlay-ports + auto maybe_port = Paragraphs::try_load_port(paths.get_filesystem(), paths.ports / port_name); if (maybe_port.error()) { Checks::exit_success(VCPKG_LINE_INFO); diff --git a/toolsrc/src/vcpkg/commands.buildexternal.cpp b/toolsrc/src/vcpkg/commands.buildexternal.cpp index 19b89c2f5..1c3c511c2 100644 --- a/toolsrc/src/vcpkg/commands.buildexternal.cpp +++ b/toolsrc/src/vcpkg/commands.buildexternal.cpp @@ -23,7 +23,12 @@ namespace vcpkg::Commands::BuildExternal std::string(args.command_arguments.at(0)), default_triplet, COMMAND_STRUCTURE.example_text); Input::check_triplet(spec.package_spec.triplet(), paths); - const fs::path port_dir = args.command_arguments.at(1); - Build::Command::perform_and_exit_ex(spec, port_dir, options, paths); + auto overlays = args.overlay_ports ? *args.overlay_ports : std::vector<std::string>(); + overlays.insert(overlays.begin(), args.command_arguments.at(1)); + Dependencies::PathsPortFileProvider provider(paths, &overlays); + auto maybe_scfl = provider.get_control_file(spec.package_spec.name()); + Checks::check_exit( + VCPKG_LINE_INFO, maybe_scfl.has_value(), "could not load control file for %s", spec.package_spec.name()); + Build::Command::perform_and_exit_ex(spec, maybe_scfl.value_or_exit(VCPKG_LINE_INFO), options, paths); } } diff --git a/toolsrc/src/vcpkg/commands.cache.cpp b/toolsrc/src/vcpkg/commands.cache.cpp index c321de3b5..4c49db004 100644 --- a/toolsrc/src/vcpkg/commands.cache.cpp +++ b/toolsrc/src/vcpkg/commands.cache.cpp @@ -14,7 +14,7 @@ namespace vcpkg::Commands::Cache std::vector<BinaryParagraph> output; for (auto&& path : paths.get_filesystem().get_files_non_recursive(paths.packages)) { - const Expected<std::unordered_map<std::string, std::string>> pghs = + const Expected<Parse::RawParagraph> pghs = Paragraphs::get_single_paragraph(paths.get_filesystem(), path / "CONTROL"); if (const auto p = pghs.get()) { diff --git a/toolsrc/src/vcpkg/commands.ci.cpp b/toolsrc/src/vcpkg/commands.ci.cpp index de66d917b..419d65f03 100644 --- a/toolsrc/src/vcpkg/commands.ci.cpp +++ b/toolsrc/src/vcpkg/commands.ci.cpp @@ -60,9 +60,10 @@ namespace vcpkg::Commands::CI void add_test_results(const std::string& spec, const Build::BuildResult& build_result, const Chrono::ElapsedTime& elapsed_time, - const std::string& abi_tag) + const std::string& abi_tag, + const std::vector<std::string>& features) { - m_collections.back().tests.push_back({spec, build_result, elapsed_time, abi_tag}); + m_collections.back().tests.push_back({spec, build_result, elapsed_time, abi_tag, features}); } // Starting a new test collection @@ -98,6 +99,7 @@ namespace vcpkg::Commands::CI vcpkg::Build::BuildResult result; vcpkg::Chrono::ElapsedTime time; std::string abi_tag; + std::vector<std::string> features; }; struct XunitCollection @@ -166,9 +168,29 @@ namespace vcpkg::Commands::CI } std::string traits_block; - if (test.abi_tag != "") // only adding if there is a known abi tag + if (test.abi_tag != "") { - traits_block = Strings::format(R"(<traits><trait name="abi_tag" value="%s" /></traits>)", test.abi_tag); + traits_block += Strings::format(R"(<trait name="abi_tag" value="%s" />)", test.abi_tag); + } + + if (!test.features.empty()) + { + std::string feature_list; + for (const auto& feature : test.features) + { + if (!feature_list.empty()) + { + feature_list += ", "; + } + feature_list += feature; + } + + traits_block += Strings::format(R"(<trait name="features" value="%s" />)", feature_list); + } + + if (!traits_block.empty()) + { + traits_block = "<traits>" + traits_block + "</traits>"; } m_xml += Strings::format(R"( <test name="%s" method="%s" time="%lld" result="%s">%s%s</test>)" @@ -212,6 +234,7 @@ namespace vcpkg::Commands::CI const Build::BuildPackageOptions build_options = { Build::UseHeadVersion::NO, Build::AllowDownloads::YES, + Build::OnlyDownloads::NO, Build::CleanBuildtrees::YES, Build::CleanPackages::YES, Build::CleanDownloads::NO, @@ -232,12 +255,11 @@ namespace vcpkg::Commands::CI { // determine abi tag std::string abi; - if (auto scf = p->source_control_file.get()) + if (auto scfl = p->source_control_file_location.get()) { auto triplet = p->spec.triplet(); - const Build::BuildPackageConfig build_config{ - *scf, triplet, paths.port_dir(p->spec), build_options, p->feature_list}; + const Build::BuildPackageConfig build_config{*scfl, triplet, build_options, p->feature_list}; auto dependency_abis = Util::fmap(p->computed_dependencies, [&](const PackageSpec& spec) -> Build::AbiEntry { @@ -249,7 +271,7 @@ namespace vcpkg::Commands::CI 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); }); + triplet, [&]() { return Build::PreBuildInfo::from_triplet_file(paths, triplet, *scfl); }); auto maybe_tag_and_file = Build::compute_abi_tag(paths, build_config, pre_build_info, dependency_abis); @@ -351,11 +373,13 @@ namespace vcpkg::Commands::CI } StatusParagraphs status_db = database_load_check(paths); - const auto& paths_port_file = Dependencies::PathsPortFileProvider(paths); + + Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); const Build::BuildPackageOptions install_plan_options = { Build::UseHeadVersion::NO, Build::AllowDownloads::YES, + Build::OnlyDownloads::NO, Build::CleanBuildtrees::YES, Build::CleanPackages::YES, Build::CleanDownloads::NO, @@ -369,7 +393,10 @@ namespace vcpkg::Commands::CI XunitTestResults xunitTestResults; - std::vector<std::string> all_ports = Install::get_all_port_names(paths); + std::vector<std::string> all_ports = + Util::fmap(provider.load_all_control_files(), [](auto&& scfl) -> std::string { + return scfl->source_control_file.get()->core_paragraph->name; + }); std::vector<TripletAndSummary> results; auto timer = Chrono::ElapsedTimer::create_started(); for (const Triplet& triplet : triplets) @@ -378,13 +405,13 @@ namespace vcpkg::Commands::CI xunitTestResults.push_collection(triplet.canonical_name()); - Dependencies::PackageGraph pgraph(paths_port_file, status_db); + Dependencies::PackageGraph pgraph(provider, status_db); std::vector<PackageSpec> specs = PackageSpec::to_package_specs(all_ports, triplet); // Install the default features for every package auto all_feature_specs = Util::fmap(specs, [](auto& spec) { return FeatureSpec(spec, ""); }); auto split_specs = - find_unknown_ports_for_ci(paths, exclusions_set, paths_port_file, all_feature_specs, purge_tombstones); + find_unknown_ports_for_ci(paths, exclusions_set, provider, all_feature_specs, purge_tombstones); auto feature_specs = FullPackageSpec::to_feature_specs(split_specs->unknown); for (auto&& fspec : feature_specs) @@ -442,7 +469,7 @@ namespace vcpkg::Commands::CI if (is_dry_run) { - Dependencies::print_plan(action_plan); + Dependencies::print_plan(action_plan, true, paths.ports); } else { @@ -453,20 +480,24 @@ namespace vcpkg::Commands::CI // Adding results for ports that were built or pulled from an archive for (auto&& result : summary.results) { + auto& port_features = split_specs->features[result.spec]; split_specs->known.erase(result.spec); xunitTestResults.add_test_results(result.spec.to_string(), result.build_result.code, result.timing, - split_specs->abi_tag_map.at(result.spec)); + split_specs->abi_tag_map.at(result.spec), + port_features); } // Adding results for ports that were not built because they have known states for (auto&& port : split_specs->known) { + auto& port_features = split_specs->features[port.first]; xunitTestResults.add_test_results(port.first.to_string(), port.second, Chrono::ElapsedTime{}, - split_specs->abi_tag_map.at(port.first)); + split_specs->abi_tag_map.at(port.first), + port_features); } all_known_results.emplace_back(std::move(split_specs->known)); diff --git a/toolsrc/src/vcpkg/commands.cpp b/toolsrc/src/vcpkg/commands.cpp index 54e9346ba..1f424a559 100644 --- a/toolsrc/src/vcpkg/commands.cpp +++ b/toolsrc/src/vcpkg/commands.cpp @@ -24,6 +24,7 @@ namespace vcpkg::Commands {"env", &Env::perform_and_exit}, {"build-external", &BuildExternal::perform_and_exit}, {"export", &Export::perform_and_exit}, + {"depend-info", &DependInfo::perform_and_exit}, }; return t; } @@ -38,7 +39,6 @@ namespace vcpkg::Commands {"integrate", &Integrate::perform_and_exit}, {"owns", &Owns::perform_and_exit}, {"update", &Update::perform_and_exit}, - {"depend-info", &DependInfo::perform_and_exit}, {"edit", &Edit::perform_and_exit}, {"create", &Create::perform_and_exit}, {"import", &Import::perform_and_exit}, @@ -47,6 +47,7 @@ namespace vcpkg::Commands {"autocomplete", &Autocomplete::perform_and_exit}, {"hash", &Hash::perform_and_exit}, {"fetch", &Fetch::perform_and_exit}, + {"x-history", &PortHistory::perform_and_exit}, {"x-vsinstances", &X_VSInstances::perform_and_exit}, }; return t; @@ -99,8 +100,15 @@ namespace vcpkg::Commands::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 = vcpkg::Hash::get_file_hash(paths.get_filesystem(), file_to_hash, algorithm); + + auto algorithm = vcpkg::Hash::Algorithm::Sha512; + if (args.command_arguments.size() == 2) + { + algorithm = vcpkg::Hash::algorithm_from_string(args.command_arguments[1]).value_or_exit(VCPKG_LINE_INFO); + } + + const std::string hash = + vcpkg::Hash::get_file_hash(VCPKG_LINE_INFO, paths.get_filesystem(), file_to_hash, algorithm); System::print2(hash, '\n'); Checks::exit_success(VCPKG_LINE_INFO); } diff --git a/toolsrc/src/vcpkg/commands.create.cpp b/toolsrc/src/vcpkg/commands.create.cpp index 31bf97f30..e04599dfe 100644 --- a/toolsrc/src/vcpkg/commands.create.cpp +++ b/toolsrc/src/vcpkg/commands.create.cpp @@ -24,7 +24,7 @@ namespace vcpkg::Commands::Create const fs::path& cmake_exe = paths.get_tool_exe(Tools::CMAKE); - std::vector<System::CMakeVariable> cmake_args{{"CMD", "CREATE"}, {"PORT", port_name}, {"URL", url}}; + std::vector<System::CMakeVariable> cmake_args{{"CMD", "CREATE"}, {"PORT", port_name}, {"URL", url}, {"VCPKG_ROOT_PATH", paths.root}}; if (args.command_arguments.size() >= 3) { diff --git a/toolsrc/src/vcpkg/commands.dependinfo.cpp b/toolsrc/src/vcpkg/commands.dependinfo.cpp index 48a289fa5..79cbba590 100644 --- a/toolsrc/src/vcpkg/commands.dependinfo.cpp +++ b/toolsrc/src/vcpkg/commands.dependinfo.cpp @@ -4,185 +4,326 @@ #include <vcpkg/base/system.print.h>
#include <vcpkg/base/util.h>
#include <vcpkg/commands.h>
+#include <vcpkg/dependencies.h>
#include <vcpkg/help.h>
-#include <vcpkg/paragraphs.h>
+#include <vcpkg/input.h>
+#include <vcpkg/install.h>
+#include <vcpkg/packagespec.h>
+#include <vector>
+
+using vcpkg::Dependencies::AnyAction;
+using vcpkg::Dependencies::create_feature_install_plan;
+using vcpkg::Dependencies::InstallPlanAction;
+using vcpkg::Dependencies::PathsPortFileProvider;
namespace vcpkg::Commands::DependInfo
{
- constexpr StringLiteral OPTION_DOT = "--dot";
- constexpr StringLiteral OPTION_DGML = "--dgml";
- constexpr StringLiteral OPTION_NO_RECURSE = "--no-recurse";
+ namespace
+ {
+ constexpr StringLiteral OPTION_DOT = "--dot";
+ constexpr StringLiteral OPTION_DGML = "--dgml";
+ constexpr StringLiteral OPTION_SHOW_DEPTH = "--show-depth";
+ constexpr StringLiteral OPTION_MAX_RECURSE = "--max-recurse";
+ constexpr StringLiteral OPTION_SORT = "--sort";
- constexpr std::array<CommandSwitch, 3> DEPEND_SWITCHES = {{
- {OPTION_DOT, "Creates graph on basis of dot"},
- {OPTION_DGML, "Creates graph on basis of dgml"},
- {OPTION_NO_RECURSE,
- "Computes only immediate dependencies of packages explicitly specified on the command-line"},
- }};
+ constexpr int NO_RECURSE_LIMIT_VALUE = -1;
- const CommandStructure COMMAND_STRUCTURE = {
- Help::create_example_string(R"###(depend-info [pat])###"),
- 0,
- SIZE_MAX,
- {DEPEND_SWITCHES, {}},
- nullptr,
- };
+ constexpr std::array<CommandSwitch, 3> DEPEND_SWITCHES = {
+ {{OPTION_DOT, "Creates graph on basis of dot"},
+ {OPTION_DGML, "Creates graph on basis of dgml"},
+ {OPTION_SHOW_DEPTH, "Show recursion depth in output"}}};
- std::string replace_dashes_with_underscore(const std::string& input)
- {
- std::string output = input;
- std::replace(output.begin(), output.end(), '-', '_');
- return output;
- }
+ constexpr std::array<CommandSetting, 2> DEPEND_SETTINGS = {
+ {{OPTION_MAX_RECURSE, "Set max recursion depth, a value of -1 indicates no limit"},
+ {OPTION_SORT,
+ "Set sort order for the list of dependencies, accepted values are: lexicographical, topological "
+ "(default), "
+ "reverse"}}};
- std::string create_dot_as_string(const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files)
- {
- int empty_node_count = 0;
+ struct PackageDependInfo
+ {
+ std::string package;
+ int depth;
+ std::set<std::string> features;
+ std::vector<std::string> dependencies;
+ };
- std::string s;
- s.append("digraph G{ rankdir=LR; edge [minlen=3]; overlap=false;");
+ enum SortMode
+ {
+ Lexicographical = 0,
+ Topological,
+ ReverseTopological,
+ Default = Topological
+ };
- for (const auto& source_control_file : source_control_files)
+ int get_max_depth(const ParsedArguments& options)
{
- const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
- if (source_paragraph.depends.empty())
+ auto iter = options.settings.find(OPTION_MAX_RECURSE);
+ if (iter != options.settings.end())
{
- empty_node_count++;
- continue;
+ std::string value = iter->second;
+ try
+ {
+ return std::stoi(value);
+ }
+ catch (std::exception&)
+ {
+ Checks::exit_with_message(VCPKG_LINE_INFO, "Value of --max-depth must be an integer");
+ }
}
+ // No --max-depth set, default to no limit.
+ return NO_RECURSE_LIMIT_VALUE;
+ }
- const std::string name = replace_dashes_with_underscore(source_paragraph.name);
- s.append(Strings::format("%s;", name));
- for (const Dependency& d : source_paragraph.depends)
+ SortMode get_sort_mode(const ParsedArguments& options)
+ {
+ constexpr StringLiteral OPTION_SORT_LEXICOGRAPHICAL = "lexicographical";
+ constexpr StringLiteral OPTION_SORT_TOPOLOGICAL = "topological";
+ constexpr StringLiteral OPTION_SORT_REVERSE = "reverse";
+
+ static const std::map<std::string, SortMode> sortModesMap{{OPTION_SORT_LEXICOGRAPHICAL, Lexicographical},
+ {OPTION_SORT_TOPOLOGICAL, Topological},
+ {OPTION_SORT_REVERSE, ReverseTopological}};
+
+ auto iter = options.settings.find(OPTION_SORT);
+ if (iter != options.settings.end())
{
- const std::string dependency_name = replace_dashes_with_underscore(d.depend.name);
- s.append(Strings::format("%s -> %s;", name, dependency_name));
+ const std::string value = Strings::ascii_to_lowercase(std::string{iter->second});
+ auto it = sortModesMap.find(value);
+ if (it != sortModesMap.end())
+ {
+ return it->second;
+ }
+ Checks::exit_with_message(VCPKG_LINE_INFO,
+ "Value of --sort must be one of `%s`, `%s`, or `%s`",
+ OPTION_SORT_LEXICOGRAPHICAL,
+ OPTION_SORT_TOPOLOGICAL,
+ OPTION_SORT_REVERSE);
}
+ return Default;
}
- s.append(Strings::format("empty [label=\"%d singletons...\"]; }", empty_node_count));
- return s;
- }
-
- std::string create_dgml_as_string(const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files)
- {
- std::string s;
- s.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
- s.append("<DirectedGraph xmlns=\"http://schemas.microsoft.com/vs/2009/dgml\">");
-
- std::string nodes, links;
- for (const auto& source_control_file : source_control_files)
+ std::string create_dot_as_string(const std::vector<PackageDependInfo>& depend_info)
{
- const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
- const std::string name = source_paragraph.name;
- nodes.append(Strings::format("<Node Id=\"%s\" />", name));
+ int empty_node_count = 0;
- // Iterate over dependencies.
- for (const Dependency& d : source_paragraph.depends)
- {
- if (d.qualifier.empty())
- links.append(Strings::format("<Link Source=\"%s\" Target=\"%s\" />", name, d.depend.name));
- else
- links.append(Strings::format(
- "<Link Source=\"%s\" Target=\"%s\" StrokeDashArray=\"4\" />", name, d.depend.name));
- }
+ std::string s;
+ s.append("digraph G{ rankdir=LR; edge [minlen=3]; overlap=false;");
- // Iterate over feature dependencies.
- const std::vector<std::unique_ptr<FeatureParagraph>>& feature_paragraphs =
- source_control_file->feature_paragraphs;
- for (const auto& feature_paragraph : feature_paragraphs)
+ for (const auto& package : depend_info)
{
- for (const Dependency& d : feature_paragraph->depends)
+ if (package.dependencies.empty())
{
- links.append(Strings::format(
- "<Link Source=\"%s\" Target=\"%s\" StrokeDashArray=\"4\" />", name, d.depend.name));
+ empty_node_count++;
+ continue;
+ }
+
+ const std::string name = Strings::replace_all(std::string{package.package}, "-", "_");
+ s.append(Strings::format("%s;", name));
+ for (const auto& d : package.dependencies)
+ {
+ const std::string dependency_name = Strings::replace_all(std::string{d}, "-", "_");
+ s.append(Strings::format("%s -> %s;", name, dependency_name));
}
}
+
+ s.append(Strings::format("empty [label=\"%d singletons...\"]; }", empty_node_count));
+ return s;
}
- s.append(Strings::format("<Nodes>%s</Nodes>", nodes));
+ std::string create_dgml_as_string(const std::vector<PackageDependInfo>& depend_info)
+ {
+ std::string s;
+ s.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+ s.append("<DirectedGraph xmlns=\"http://schemas.microsoft.com/vs/2009/dgml\">");
- s.append(Strings::format("<Links>%s</Links>", links));
+ std::string nodes, links;
+ for (const auto& package : depend_info)
+ {
+ const std::string name = package.package;
+ nodes.append(Strings::format("<Node Id=\"%s\" />", name));
- s.append("</DirectedGraph>");
- return s;
- }
+ // Iterate over dependencies.
+ for (const auto& d : package.dependencies)
+ {
+ links.append(Strings::format("<Link Source=\"%s\" Target=\"%s\" />", name, d));
+ }
+ }
- std::string create_graph_as_string(const std::unordered_set<std::string>& switches,
- const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files)
- {
- if (Util::Sets::contains(switches, OPTION_DOT))
- {
- return create_dot_as_string(source_control_files);
+ s.append(Strings::format("<Nodes>%s</Nodes>", nodes));
+
+ s.append(Strings::format("<Links>%s</Links>", links));
+
+ s.append("</DirectedGraph>");
+ return s;
}
- else if (Util::Sets::contains(switches, OPTION_DGML))
+
+ std::string create_graph_as_string(const std::unordered_set<std::string>& switches,
+ const std::vector<PackageDependInfo>& depend_info)
{
- return create_dgml_as_string(source_control_files);
+ if (Util::Sets::contains(switches, OPTION_DOT))
+ {
+ return create_dot_as_string(depend_info);
+ }
+ else if (Util::Sets::contains(switches, OPTION_DGML))
+ {
+ return create_dgml_as_string(depend_info);
+ }
+ return "";
}
- return "";
- }
- void build_dependencies_list(std::set<std::string>& packages_to_keep,
- const std::string& requested_package,
- const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files,
- const std::unordered_set<std::string>& switches)
- {
- const auto source_control_file =
- Util::find_if(source_control_files, [&requested_package](const auto& source_control_file) {
- return source_control_file->core_paragraph->name == requested_package;
- });
-
- if (source_control_file != source_control_files.end())
+ void assign_depth_to_dependencies(const std::string& package,
+ const int depth,
+ const int max_depth,
+ std::map<std::string, PackageDependInfo>& dependencies_map)
{
- const auto new_package = packages_to_keep.insert(requested_package).second;
+ auto iter = dependencies_map.find(package);
+ Checks::check_exit(
+ VCPKG_LINE_INFO, iter != dependencies_map.end(), "Package not found in dependency graph");
+
+ PackageDependInfo& info = iter->second;
- if (new_package && !Util::Sets::contains(switches, OPTION_NO_RECURSE))
+ if (depth > info.depth)
{
- for (const auto& dependency : (*source_control_file)->core_paragraph->depends)
+ info.depth = depth;
+ if (depth < max_depth || max_depth == NO_RECURSE_LIMIT_VALUE)
{
- build_dependencies_list(packages_to_keep, dependency.depend.name, source_control_files, switches);
+ for (auto&& dependency : info.dependencies)
+ {
+ assign_depth_to_dependencies(dependency, depth + 1, max_depth, dependencies_map);
+ }
}
}
}
- else
+
+ std::vector<PackageDependInfo> extract_depend_info(const std::vector<const InstallPlanAction*>& install_actions,
+ const int max_depth)
{
- System::print2(System::Color::warning, "package '", requested_package, "' does not exist\n");
+ std::map<std::string, PackageDependInfo> package_dependencies;
+ for (const InstallPlanAction* pia : install_actions)
+ {
+ const InstallPlanAction& install_action = *pia;
+
+ const std::vector<std::string> dependencies = Util::fmap(
+ install_action.computed_dependencies, [](const PackageSpec& spec) { return spec.name(); });
+
+ std::set<std::string> features{install_action.feature_list};
+ features.erase("core");
+
+ std::string port_name = install_action.spec.name();
+
+ PackageDependInfo info{port_name, -1, features, dependencies};
+ package_dependencies.emplace(port_name, std::move(info));
+ }
+
+ const InstallPlanAction& init = *install_actions.back();
+ assign_depth_to_dependencies(init.spec.name(), 0, max_depth, package_dependencies);
+
+ std::vector<PackageDependInfo> out =
+ Util::fmap(package_dependencies, [](auto&& kvpair) -> PackageDependInfo { return kvpair.second; });
+ Util::erase_remove_if(out, [](auto&& info) { return info.depth < 0; });
+ return out;
}
}
- void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
+ const CommandStructure COMMAND_STRUCTURE = {
+ Help::create_example_string("depend-info sqlite3"),
+ 1,
+ 1,
+ {DEPEND_SWITCHES, DEPEND_SETTINGS},
+ nullptr,
+ };
+
+ void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet)
{
const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE);
+ const int max_depth = get_max_depth(options);
+ const SortMode sort_mode = get_sort_mode(options);
+ const bool show_depth = Util::Sets::contains(options.switches, OPTION_SHOW_DEPTH);
- auto source_control_files = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports);
+ const std::vector<FullPackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) {
+ return Input::check_and_get_full_package_spec(
+ std::string{arg}, default_triplet, COMMAND_STRUCTURE.example_text);
+ });
- if (args.command_arguments.size() >= 1)
+ for (auto&& spec : specs)
{
- std::set<std::string> packages_to_keep;
- for (const auto& requested_package : args.command_arguments)
+ Input::check_triplet(spec.package_spec.triplet(), paths);
+ }
+
+ PathsPortFileProvider provider(paths, args.overlay_ports.get());
+
+ // By passing an empty status_db, we should get a plan containing all dependencies.
+ // All actions in the plan should be install actions, as there's no installed packages to remove.
+ StatusParagraphs status_db;
+ std::vector<AnyAction> action_plan =
+ create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db);
+ std::vector<const InstallPlanAction*> install_actions = Util::fmap(action_plan, [&](const AnyAction& action) {
+ if (auto install_action = action.install_action.get())
{
- build_dependencies_list(packages_to_keep, requested_package, source_control_files, options.switches);
+ return install_action;
}
+ Checks::exit_with_message(VCPKG_LINE_INFO, "Only install actions should exist in the plan");
+ });
- Util::erase_remove_if(source_control_files, [&packages_to_keep](const auto& source_control_file) {
- return !Util::Sets::contains(packages_to_keep, source_control_file->core_paragraph->name);
- });
- }
+ std::vector<PackageDependInfo> depend_info = extract_depend_info(install_actions, max_depth);
if (Util::Sets::contains(options.switches, OPTION_DOT) || Util::Sets::contains(options.switches, OPTION_DGML))
{
- const std::string graph_as_string = create_graph_as_string(options.switches, source_control_files);
+ const std::vector<const SourceControlFile*> source_control_files =
+ Util::fmap(install_actions, [](const InstallPlanAction* install_action) {
+ const SourceControlFileLocation& scfl =
+ install_action->source_control_file_location.value_or_exit(VCPKG_LINE_INFO);
+ return const_cast<const SourceControlFile*>(scfl.source_control_file.get());
+ });
+
+ const std::string graph_as_string = create_graph_as_string(options.switches, depend_info);
System::print2(graph_as_string, '\n');
Checks::exit_success(VCPKG_LINE_INFO);
}
- for (auto&& source_control_file : source_control_files)
+ // TODO: Improve this code
+ auto lex = [](const PackageDependInfo& lhs, const PackageDependInfo& rhs) -> bool {
+ return lhs.package < rhs.package;
+ };
+ auto topo = [](const PackageDependInfo& lhs, const PackageDependInfo& rhs) -> bool {
+ return lhs.depth > rhs.depth;
+ };
+ auto reverse = [](const PackageDependInfo& lhs, const PackageDependInfo& rhs) -> bool {
+ return lhs.depth < rhs.depth;
+ };
+
+ switch (sort_mode)
{
- const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
- const auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name(); });
- System::print2(source_paragraph.name, ": ", s, "\n");
+ case SortMode::Lexicographical: std::sort(std::begin(depend_info), std::end(depend_info), lex); break;
+ case SortMode::ReverseTopological:
+ std::sort(std::begin(depend_info), std::end(depend_info), reverse);
+ break;
+ case SortMode::Topological: std::sort(std::begin(depend_info), std::end(depend_info), topo); break;
+ default: Checks::unreachable(VCPKG_LINE_INFO);
}
+ for (auto&& info : depend_info)
+ {
+ if (info.depth >= 0)
+ {
+ std::string features = Strings::join(", ", info.features);
+ const std::string dependencies = Strings::join(", ", info.dependencies);
+
+ if (show_depth)
+ {
+ System::print2(System::Color::error, "(", info.depth, ") ");
+ }
+ System::print2(System::Color::success, info.package);
+ if (!features.empty())
+ {
+ System::print2("[");
+ System::print2(System::Color::warning, features);
+ System::print2("]");
+ }
+ System::print2(": ", dependencies, "\n");
+ }
+ }
Checks::exit_success(VCPKG_LINE_INFO);
}
}
diff --git a/toolsrc/src/vcpkg/commands.edit.cpp b/toolsrc/src/vcpkg/commands.edit.cpp index 98176b2b0..314af1167 100644 --- a/toolsrc/src/vcpkg/commands.edit.cpp +++ b/toolsrc/src/vcpkg/commands.edit.cpp @@ -9,11 +9,11 @@ namespace vcpkg::Commands::Edit { +#if defined(_WIN32) static std::vector<fs::path> find_from_registry() { std::vector<fs::path> output; -#if defined(_WIN32) struct RegKey { HKEY root; @@ -42,9 +42,9 @@ namespace vcpkg::Commands::Edit output.push_back(install_path / "Code.exe"); } } -#endif return output; } +#endif static constexpr StringLiteral OPTION_BUILDTREES = "--buildtrees"; @@ -79,6 +79,7 @@ namespace vcpkg::Commands::Edit const auto& fs = paths.get_filesystem(); auto packages = fs.get_files_non_recursive(paths.packages); + // TODO: Support edit for --overlay-ports return Util::fmap(ports, [&](const std::string& port_name) -> std::string { const auto portpath = paths.ports / port_name; const auto portfile = portpath / "portfile.cmake"; @@ -166,6 +167,22 @@ namespace vcpkg::Commands::Edit const std::vector<fs::path> from_registry = find_from_registry(); candidate_paths.insert(candidate_paths.end(), from_registry.cbegin(), from_registry.cend()); + + const auto txt_default = System::get_registry_string(HKEY_CLASSES_ROOT, R"(.txt\ShellNew)", "ItemName"); + if(const auto entry = txt_default.get()) + { + #ifdef UNICODE + LPWSTR dst = new wchar_t[MAX_PATH]; + ExpandEnvironmentStrings(Strings::to_utf16(*entry).c_str(), dst, MAX_PATH); + auto full_path = Strings::to_utf8(dst); + #else + LPSTR dst = new char[MAX_PATH]; + ExpandEnvironmentStrings(entry->c_str(), dst, MAX_PATH); + auto full_path = std::string(dst); + #endif + auto begin = full_path.find_first_not_of('@'); + candidate_paths.push_back(fs::u8path(full_path.substr(begin, full_path.find_first_of(',')-begin))); + } #elif defined(__APPLE__) candidate_paths.push_back( fs::path{"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"}); @@ -173,6 +190,22 @@ namespace vcpkg::Commands::Edit #elif defined(__linux__) candidate_paths.push_back(fs::path{"/usr/share/code/bin/code"}); candidate_paths.push_back(fs::path{"/usr/bin/code"}); + + if(System::cmd_execute("command -v xdg-mime") == 0) + { + auto mime_qry = Strings::format(R"(xdg-mime query default text/plain)"); + auto execute_result = System::cmd_execute_and_capture_output(mime_qry); + if(execute_result.exit_code == 0 && !execute_result.output.empty()) + { + mime_qry = Strings::format(R"(command -v %s)", execute_result.output.substr(0, execute_result.output.find('.'))); + execute_result = System::cmd_execute_and_capture_output(mime_qry); + if(execute_result.exit_code == 0 && !execute_result.output.empty()) + { + execute_result.output.erase(std::remove(std::begin(execute_result.output), std::end(execute_result.output), '\n'), std::end(execute_result.output)); + candidate_paths.push_back(fs::path{execute_result.output}); + } + } + } #endif const auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { return fs.exists(p); }); diff --git a/toolsrc/src/vcpkg/commands.exportifw.cpp b/toolsrc/src/vcpkg/commands.exportifw.cpp index f0946110c..bc75069cd 100644 --- a/toolsrc/src/vcpkg/commands.exportifw.cpp +++ b/toolsrc/src/vcpkg/commands.exportifw.cpp @@ -13,86 +13,94 @@ namespace vcpkg::Export::IFW using Dependencies::ExportPlanType; using Install::InstallDir; - static std::string create_release_date() + namespace { - const tm date_time = Chrono::get_current_date_time_local(); + std::string create_release_date() + { + const tm date_time = Chrono::get_current_date_time_local(); - // Format is: YYYY-mm-dd - // 10 characters + 1 null terminating character will be written for a total of 11 chars - char mbstr[11]; - const size_t bytes_written = std::strftime(mbstr, sizeof(mbstr), "%Y-%m-%d", &date_time); - Checks::check_exit(VCPKG_LINE_INFO, - bytes_written == 10, - "Expected 10 bytes to be written, but %u were written", - bytes_written); - const std::string date_time_as_string(mbstr); - return date_time_as_string; - } + // Format is: YYYY-mm-dd + // 10 characters + 1 null terminating character will be written for a total of 11 chars + char mbstr[11]; + const size_t bytes_written = std::strftime(mbstr, sizeof(mbstr), "%Y-%m-%d", &date_time); + Checks::check_exit(VCPKG_LINE_INFO, + bytes_written == 10, + "Expected 10 bytes to be written, but %u were written", + bytes_written); + const std::string date_time_as_string(mbstr); + return date_time_as_string; + } - std::string safe_rich_from_plain_text(const std::string& text) - { - // match standalone ampersand, no HTML number or name - std::regex standalone_ampersand(R"###(&(?!(#[0-9]+|\w+);))###"); + std::string safe_rich_from_plain_text(const std::string& text) + { + // match standalone ampersand, no HTML number or name + std::regex standalone_ampersand(R"###(&(?!(#[0-9]+|\w+);))###"); - return std::regex_replace(text, standalone_ampersand, "&"); - } + return std::regex_replace(text, standalone_ampersand, "&"); + } - fs::path get_packages_dir_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - return ifw_options.maybe_packages_dir_path.has_value() - ? fs::path(ifw_options.maybe_packages_dir_path.value_or_exit(VCPKG_LINE_INFO)) - : paths.root / (export_id + "-ifw-packages"); - } + fs::path get_packages_dir_path(const std::string& export_id, + const Options& ifw_options, + const VcpkgPaths& paths) + { + return ifw_options.maybe_packages_dir_path.has_value() + ? fs::path(ifw_options.maybe_packages_dir_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-packages"); + } - fs::path get_repository_dir_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - return ifw_options.maybe_repository_dir_path.has_value() - ? fs::path(ifw_options.maybe_repository_dir_path.value_or_exit(VCPKG_LINE_INFO)) - : paths.root / (export_id + "-ifw-repository"); - } + fs::path get_repository_dir_path(const std::string& export_id, + const Options& ifw_options, + const VcpkgPaths& paths) + { + return ifw_options.maybe_repository_dir_path.has_value() + ? fs::path(ifw_options.maybe_repository_dir_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-repository"); + } - fs::path get_config_file_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - return ifw_options.maybe_config_file_path.has_value() - ? fs::path(ifw_options.maybe_config_file_path.value_or_exit(VCPKG_LINE_INFO)) - : paths.root / (export_id + "-ifw-configuration.xml"); - } + fs::path get_config_file_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + return ifw_options.maybe_config_file_path.has_value() + ? fs::path(ifw_options.maybe_config_file_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-configuration.xml"); + } - fs::path get_installer_file_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - return ifw_options.maybe_installer_file_path.has_value() - ? fs::path(ifw_options.maybe_installer_file_path.value_or_exit(VCPKG_LINE_INFO)) - : paths.root / (export_id + "-ifw-installer.exe"); - } + fs::path get_installer_file_path(const std::string& export_id, + const Options& ifw_options, + const VcpkgPaths& paths) + { + return ifw_options.maybe_installer_file_path.has_value() + ? fs::path(ifw_options.maybe_installer_file_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-installer.exe"); + } - fs::path export_real_package(const fs::path& ifw_packages_dir_path, - const ExportPlanAction& action, - Files::Filesystem& fs) - { - std::error_code ec; + fs::path export_real_package(const fs::path& ifw_packages_dir_path, + const ExportPlanAction& action, + Files::Filesystem& fs) + { + std::error_code ec; - const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); - // Prepare meta dir - const fs::path package_xml_file_path = - ifw_packages_dir_path / - Strings::format("packages.%s.%s", action.spec.name(), action.spec.triplet().canonical_name()) / "meta" / - "package.xml"; - const fs::path package_xml_dir_path = package_xml_file_path.parent_path(); - fs.create_directories(package_xml_dir_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not create directory for package file %s", - package_xml_file_path.generic_u8string()); + // Prepare meta dir + const fs::path package_xml_file_path = + ifw_packages_dir_path / + Strings::format("packages.%s.%s", action.spec.name(), action.spec.triplet().canonical_name()) / "meta" / + "package.xml"; + const fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_u8string()); - auto deps = Strings::join( - ",", binary_paragraph.depends, [](const std::string& dep) { return "packages." + dep + ":"; }); + auto deps = Strings::join( + ",", binary_paragraph.depends, [](const std::string& dep) { return "packages." + dep + ":"; }); - if (!deps.empty()) deps = "\n <Dependencies>" + deps + "</Dependencies>"; + if (!deps.empty()) deps = "\n <Dependencies>" + deps + "</Dependencies>"; - fs.write_contents(package_xml_file_path, - Strings::format( - R"###(<?xml version="1.0"?> + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> <Package> <DisplayName>%s</DisplayName> <Version>%s</Version> @@ -101,111 +109,89 @@ namespace vcpkg::Export::IFW <Virtual>true</Virtual> </Package> )###", - action.spec.to_string(), - binary_paragraph.version, - create_release_date(), - action.spec.name(), - action.spec.triplet().canonical_name(), - deps), - VCPKG_LINE_INFO); - - // Return dir path for export package data - return ifw_packages_dir_path / - Strings::format("packages.%s.%s", action.spec.name(), action.spec.triplet().canonical_name()) / "data" / - "installed"; - } - - void export_unique_packages(const fs::path& raw_exported_dir_path, - std::map<std::string, const ExportPlanAction*> unique_packages, - Files::Filesystem& fs) - { - std::error_code ec; - - // packages + action.spec.to_string(), + binary_paragraph.version, + create_release_date(), + action.spec.name(), + action.spec.triplet().canonical_name(), + deps), + VCPKG_LINE_INFO); - fs::path package_xml_file_path = raw_exported_dir_path / "packages" / "meta" / "package.xml"; - fs::path package_xml_dir_path = package_xml_file_path.parent_path(); - fs.create_directories(package_xml_dir_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not create directory for package file %s", - package_xml_file_path.generic_u8string()); - fs.write_contents(package_xml_file_path, - Strings::format( - R"###(<?xml version="1.0"?> -<Package> - <DisplayName>Packages</DisplayName> - <Version>1.0.0</Version> - <ReleaseDate>%s</ReleaseDate> -</Package> -)###", - create_release_date()), - VCPKG_LINE_INFO); + // Return dir path for export package data + return ifw_packages_dir_path / + Strings::format("packages.%s.%s", action.spec.name(), action.spec.triplet().canonical_name()) / + "data" / "installed"; + } - for (const auto& unique_package : unique_packages) + void export_unique_packages(const fs::path& raw_exported_dir_path, + std::map<std::string, const ExportPlanAction*> unique_packages, + Files::Filesystem& fs) { - const ExportPlanAction& action = *(unique_package.second); - const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + std::error_code ec; + + // packages - package_xml_file_path = - raw_exported_dir_path / Strings::format("packages.%s", unique_package.first) / "meta" / "package.xml"; - package_xml_dir_path = package_xml_file_path.parent_path(); + fs::path package_xml_file_path = raw_exported_dir_path / "packages" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); fs.create_directories(package_xml_dir_path, ec); Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not create directory for package file %s", package_xml_file_path.generic_u8string()); - fs.write_contents(package_xml_file_path, Strings::format( R"###(<?xml version="1.0"?> <Package> - <DisplayName>%s</DisplayName> - <Description>%s</Description> - <Version>%s</Version> + <DisplayName>Packages</DisplayName> + <Version>1.0.0</Version> <ReleaseDate>%s</ReleaseDate> </Package> )###", - action.spec.name(), - safe_rich_from_plain_text(binary_paragraph.description), - binary_paragraph.version, create_release_date()), VCPKG_LINE_INFO); - } - } - void export_unique_triplets(const fs::path& raw_exported_dir_path, - std::set<std::string> unique_triplets, - Files::Filesystem& fs) - { - std::error_code ec; - - // triplets - - fs::path package_xml_file_path = raw_exported_dir_path / "triplets" / "meta" / "package.xml"; - fs::path package_xml_dir_path = package_xml_file_path.parent_path(); - fs.create_directories(package_xml_dir_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not create directory for package file %s", - package_xml_file_path.generic_u8string()); - fs.write_contents(package_xml_file_path, - Strings::format( - R"###(<?xml version="1.0"?> + for (const auto& unique_package : unique_packages) + { + const ExportPlanAction& action = *(unique_package.second); + const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + + package_xml_file_path = raw_exported_dir_path / Strings::format("packages.%s", unique_package.first) / + "meta" / "package.xml"; + package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_u8string()); + + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> <Package> - <DisplayName>Triplets</DisplayName> - <Version>1.0.0</Version> + <DisplayName>%s</DisplayName> + <Description>%s</Description> + <Version>%s</Version> <ReleaseDate>%s</ReleaseDate> </Package> )###", - create_release_date()), - VCPKG_LINE_INFO); + action.spec.name(), + safe_rich_from_plain_text(binary_paragraph.description), + binary_paragraph.version, + create_release_date()), + VCPKG_LINE_INFO); + } + } - for (const std::string& triplet : unique_triplets) + void export_unique_triplets(const fs::path& raw_exported_dir_path, + std::set<std::string> unique_triplets, + Files::Filesystem& fs) { - package_xml_file_path = - raw_exported_dir_path / Strings::format("triplets.%s", triplet) / "meta" / "package.xml"; - package_xml_dir_path = package_xml_file_path.parent_path(); + std::error_code ec; + + // triplets + + fs::path package_xml_file_path = raw_exported_dir_path / "triplets" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); fs.create_directories(package_xml_dir_path, ec); Checks::check_exit(VCPKG_LINE_INFO, !ec, @@ -215,73 +201,95 @@ namespace vcpkg::Export::IFW Strings::format( R"###(<?xml version="1.0"?> <Package> - <DisplayName>%s</DisplayName> + <DisplayName>Triplets</DisplayName> <Version>1.0.0</Version> <ReleaseDate>%s</ReleaseDate> </Package> )###", - triplet, create_release_date()), VCPKG_LINE_INFO); + + for (const std::string& triplet : unique_triplets) + { + package_xml_file_path = + raw_exported_dir_path / Strings::format("triplets.%s", triplet) / "meta" / "package.xml"; + package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_u8string()); + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>%s</DisplayName> + <Version>1.0.0</Version> + <ReleaseDate>%s</ReleaseDate> +</Package> +)###", + triplet, + create_release_date()), + VCPKG_LINE_INFO); + } } - } - void export_integration(const fs::path& raw_exported_dir_path, Files::Filesystem& fs) - { - std::error_code ec; + void export_integration(const fs::path& raw_exported_dir_path, Files::Filesystem& fs) + { + std::error_code ec; - // integration - fs::path package_xml_file_path = raw_exported_dir_path / "integration" / "meta" / "package.xml"; - fs::path package_xml_dir_path = package_xml_file_path.parent_path(); - fs.create_directories(package_xml_dir_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not create directory for package file %s", - package_xml_file_path.generic_u8string()); + // integration + fs::path package_xml_file_path = raw_exported_dir_path / "integration" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_u8string()); - fs.write_contents(package_xml_file_path, - Strings::format( - R"###(<?xml version="1.0"?> + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> <Package> <DisplayName>Integration</DisplayName> <Version>1.0.0</Version> <ReleaseDate>%s</ReleaseDate> </Package> )###", - create_release_date()), - VCPKG_LINE_INFO); - } + create_release_date()), + VCPKG_LINE_INFO); + } - void export_config(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - std::error_code ec; - Files::Filesystem& fs = paths.get_filesystem(); + void export_config(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + std::error_code ec; + Files::Filesystem& fs = paths.get_filesystem(); - const fs::path config_xml_file_path = get_config_file_path(export_id, ifw_options, paths); + const fs::path config_xml_file_path = get_config_file_path(export_id, ifw_options, paths); - fs::path config_xml_dir_path = config_xml_file_path.parent_path(); - fs.create_directories(config_xml_dir_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not create directory for configuration file %s", - config_xml_file_path.generic_u8string()); + fs::path config_xml_dir_path = config_xml_file_path.parent_path(); + fs.create_directories(config_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for configuration file %s", + config_xml_file_path.generic_u8string()); - std::string formatted_repo_url; - std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); - if (!ifw_repo_url.empty()) - { - formatted_repo_url = Strings::format(R"###( + std::string formatted_repo_url; + std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); + if (!ifw_repo_url.empty()) + { + formatted_repo_url = Strings::format(R"###( <RemoteRepositories> <Repository> <Url>%s</Url> </Repository> </RemoteRepositories>)###", - ifw_repo_url); - } + ifw_repo_url); + } - fs.write_contents(config_xml_file_path, - Strings::format( - R"###(<?xml version="1.0"?> + fs.write_contents(config_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> <Installer> <Name>vcpkg</Name> <Version>1.0.0</Version> @@ -289,38 +297,38 @@ namespace vcpkg::Export::IFW <TargetDir>@RootDir@/src/vcpkg</TargetDir>%s </Installer> )###", - formatted_repo_url), - VCPKG_LINE_INFO); - } + formatted_repo_url), + VCPKG_LINE_INFO); + } - void export_maintenance_tool(const fs::path& ifw_packages_dir_path, const VcpkgPaths& paths) - { - System::print2("Exporting maintenance tool...\n"); + void export_maintenance_tool(const fs::path& ifw_packages_dir_path, const VcpkgPaths& paths) + { + System::print2("Exporting maintenance tool...\n"); - std::error_code ec; - Files::Filesystem& fs = paths.get_filesystem(); + std::error_code ec; + Files::Filesystem& fs = paths.get_filesystem(); - 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, - !ec, - "Could not create directory for package file %s", - tempmaintenancetool.generic_u8string()); - fs.copy_file(installerbase_exe, tempmaintenancetool, fs::copy_options::overwrite_existing, ec); - Checks::check_exit( - VCPKG_LINE_INFO, !ec, "Could not write package file %s", tempmaintenancetool.generic_u8string()); + 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, + !ec, + "Could not create directory for package file %s", + tempmaintenancetool.generic_u8string()); + fs.copy_file(installerbase_exe, tempmaintenancetool, fs::copy_options::overwrite_existing, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not write package file %s", tempmaintenancetool.generic_u8string()); - fs::path package_xml_file_path = ifw_packages_dir_path / "maintenance" / "meta" / "package.xml"; - fs::path package_xml_dir_path = package_xml_file_path.parent_path(); - fs.create_directories(package_xml_dir_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not create directory for package file %s", - package_xml_file_path.generic_u8string()); - fs.write_contents(package_xml_file_path, - Strings::format( - R"###(<?xml version="1.0"?> + fs::path package_xml_file_path = ifw_packages_dir_path / "maintenance" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_u8string()); + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> <Package> <DisplayName>Maintenance Tool</DisplayName> <Description>Maintenance Tool</Description> @@ -332,80 +340,84 @@ namespace vcpkg::Export::IFW <ForcedInstallation>true</ForcedInstallation> </Package> )###", - create_release_date()), - VCPKG_LINE_INFO); - const fs::path script_source = paths.root / "scripts" / "ifw" / "maintenance.qs"; - const fs::path script_destination = ifw_packages_dir_path / "maintenance" / "meta" / "maintenance.qs"; - fs.copy_file(script_source, script_destination, fs::copy_options::overwrite_existing, ec); - Checks::check_exit( - VCPKG_LINE_INFO, !ec, "Could not write package file %s", script_destination.generic_u8string()); + create_release_date()), + VCPKG_LINE_INFO); + const fs::path script_source = paths.root / "scripts" / "ifw" / "maintenance.qs"; + const fs::path script_destination = ifw_packages_dir_path / "maintenance" / "meta" / "maintenance.qs"; + fs.copy_file(script_source, script_destination, fs::copy_options::overwrite_existing, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not write package file %s", script_destination.generic_u8string()); - System::print2("Exporting maintenance tool... done\n"); - } + System::print2("Exporting maintenance tool... done\n"); + } - void do_repository(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - 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); + void do_repository(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + 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); - System::print2("Generating repository ", repository_dir.generic_u8string(), "...\n"); + System::print2("Generating repository ", repository_dir.generic_u8string(), "...\n"); - std::error_code ec; - Files::Filesystem& fs = paths.get_filesystem(); + std::error_code ec; + fs::path failure_point; + Files::Filesystem& fs = paths.get_filesystem(); - fs.remove_all(repository_dir, ec); - Checks::check_exit(VCPKG_LINE_INFO, - !ec, - "Could not remove outdated repository directory %s", - repository_dir.generic_u8string()); + fs.remove_all(repository_dir, ec, failure_point); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not remove outdated repository directory %s due to file %s", + repository_dir.generic_u8string(), + failure_point.string()); - const auto cmd_line = Strings::format(R"("%s" --packages "%s" "%s" > nul)", - repogen_exe.u8string(), - packages_dir.u8string(), - repository_dir.u8string()); + const auto cmd_line = Strings::format(R"("%s" --packages "%s" "%s" > nul)", + repogen_exe.u8string(), + packages_dir.u8string(), + repository_dir.u8string()); - const int exit_code = System::cmd_execute_clean(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: IFW repository generating failed"); + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: IFW repository generating failed"); - System::printf( - System::Color::success, "Generating repository %s... done.\n", repository_dir.generic_u8string()); - } + System::printf( + System::Color::success, "Generating repository %s... done.\n", repository_dir.generic_u8string()); + } - void do_installer(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) - { - 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); - const fs::path installer_file = get_installer_file_path(export_id, ifw_options, paths); + void do_installer(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + 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); + const fs::path installer_file = get_installer_file_path(export_id, ifw_options, paths); - System::printf("Generating installer %s...\n", installer_file.generic_u8string()); + System::printf("Generating installer %s...\n", installer_file.generic_u8string()); - std::string cmd_line; + std::string cmd_line; - std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); - if (!ifw_repo_url.empty()) - { - cmd_line = Strings::format(R"("%s" --online-only --config "%s" --repository "%s" "%s" > nul)", - binarycreator_exe.u8string(), - config_file.u8string(), - repository_dir.u8string(), - installer_file.u8string()); - } - else - { - cmd_line = Strings::format(R"("%s" --config "%s" --packages "%s" "%s" > nul)", - binarycreator_exe.u8string(), - config_file.u8string(), - packages_dir.u8string(), - installer_file.u8string()); - } + std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); + if (!ifw_repo_url.empty()) + { + cmd_line = Strings::format(R"("%s" --online-only --config "%s" --repository "%s" "%s" > nul)", + binarycreator_exe.u8string(), + config_file.u8string(), + repository_dir.u8string(), + installer_file.u8string()); + } + else + { + cmd_line = Strings::format(R"("%s" --config "%s" --packages "%s" "%s" > nul)", + binarycreator_exe.u8string(), + config_file.u8string(), + packages_dir.u8string(), + installer_file.u8string()); + } - const int exit_code = System::cmd_execute_clean(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: IFW installer generating failed"); + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: IFW installer generating failed"); - System::printf(System::Color::success, "Generating installer %s... done.\n", installer_file.generic_u8string()); + System::printf( + System::Color::success, "Generating installer %s... done.\n", installer_file.generic_u8string()); + } } void do_export(const std::vector<ExportPlanAction>& export_plan, @@ -414,16 +426,18 @@ namespace vcpkg::Export::IFW const VcpkgPaths& paths) { std::error_code ec; + fs::path failure_point; Files::Filesystem& fs = paths.get_filesystem(); // Prepare packages directory const fs::path ifw_packages_dir_path = get_packages_dir_path(export_id, ifw_options, paths); - fs.remove_all(ifw_packages_dir_path, ec); + fs.remove_all(ifw_packages_dir_path, ec, failure_point); Checks::check_exit(VCPKG_LINE_INFO, !ec, - "Could not remove outdated packages directory %s", - ifw_packages_dir_path.generic_u8string()); + "Could not remove outdated packages directory %s due to file %s", + ifw_packages_dir_path.generic_u8string(), + failure_point.string()); fs.create_directory(ifw_packages_dir_path, ec); Checks::check_exit( diff --git a/toolsrc/src/vcpkg/commands.import.cpp b/toolsrc/src/vcpkg/commands.import.cpp index 40f5a434c..c18d788c5 100644 --- a/toolsrc/src/vcpkg/commands.import.cpp +++ b/toolsrc/src/vcpkg/commands.import.cpp @@ -108,7 +108,7 @@ namespace vcpkg::Commands::Import const fs::path include_directory(args.command_arguments[1]); const fs::path project_directory(args.command_arguments[2]); - const Expected<std::unordered_map<std::string, std::string>> pghs = + const Expected<Parse::RawParagraph> pghs = Paragraphs::get_single_paragraph(paths.get_filesystem(), control_file_path); Checks::check_exit(VCPKG_LINE_INFO, pghs.get() != nullptr, diff --git a/toolsrc/src/vcpkg/commands.integrate.cpp b/toolsrc/src/vcpkg/commands.integrate.cpp index 6921a5390..8b2b377bf 100644 --- a/toolsrc/src/vcpkg/commands.integrate.cpp +++ b/toolsrc/src/vcpkg/commands.integrate.cpp @@ -380,17 +380,10 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console static constexpr StringLiteral TITLE = "PowerShell Tab-Completion"; const fs::path script_path = paths.scripts / "addPoshVcpkgToPowershellProfile.ps1"; - // Console font corruption workaround - SetConsoleCP(437); - SetConsoleOutputCP(437); - + const auto& ps = paths.get_tool_exe("powershell-core"); const std::string cmd = Strings::format( - R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' %s}")", script_path.u8string(), ""); + R"("%s" -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' }")", ps.u8string(), script_path.u8string()); const int rc = System::cmd_execute(cmd); - - SetConsoleCP(CP_UTF8); - SetConsoleOutputCP(CP_UTF8); - if (rc) { System::printf(System::Color::error, @@ -409,7 +402,7 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console Checks::exit_with_code(VCPKG_LINE_INFO, rc); } -#elif defined(__unix__) +#else static void integrate_bash(const VcpkgPaths& paths) { const auto home_path = System::get_environment_variable("HOME").value_or_exit(VCPKG_LINE_INFO); @@ -458,11 +451,12 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console "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"; + " 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"; + " vcpkg integrate remove Remove user-wide integration\n" + " vcpkg integrate bash Enable bash tab-completion\n"; #endif namespace Subcommand @@ -476,7 +470,15 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console static std::vector<std::string> valid_arguments(const VcpkgPaths&) { - return {Subcommand::INSTALL, Subcommand::REMOVE, Subcommand::PROJECT, Subcommand::POWERSHELL, Subcommand::BASH}; + return + { + Subcommand::INSTALL, Subcommand::REMOVE, +#if defined(_WIN32) + Subcommand::PROJECT, Subcommand::POWERSHELL, +#else + Subcommand::BASH, +#endif + }; } const CommandStructure COMMAND_STRUCTURE = { @@ -510,7 +512,7 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console { return integrate_powershell(paths); } -#elif defined(__unix__) +#else if (args.command_arguments[0] == Subcommand::BASH) { return integrate_bash(paths); diff --git a/toolsrc/src/vcpkg/commands.porthistory.cpp b/toolsrc/src/vcpkg/commands.porthistory.cpp new file mode 100644 index 000000000..44fccbb7f --- /dev/null +++ b/toolsrc/src/vcpkg/commands.porthistory.cpp @@ -0,0 +1,91 @@ +#include "pch.h" + +#include <vcpkg/commands.h> +#include <vcpkg/help.h> + +#include <vcpkg/base/system.print.h> +#include <vcpkg/base/system.process.h> +#include <vcpkg/base/util.h> + +namespace vcpkg::Commands::PortHistory +{ + struct PortControlVersion + { + std::string commit_id; + std::string version; + std::string date; + }; + + static System::ExitCodeAndOutput run_git_command(const VcpkgPaths& paths, const std::string& cmd) + { + const fs::path& git_exe = paths.get_tool_exe(Tools::GIT); + const fs::path dot_git_dir = paths.root / ".git"; + + const std::string full_cmd = + Strings::format(R"("%s" --git-dir="%s" %s)", git_exe.u8string(), dot_git_dir.u8string(), cmd); + + auto output = System::cmd_execute_and_capture_output(full_cmd); + Checks::check_exit(VCPKG_LINE_INFO, output.exit_code == 0, "Failed to run command: %s", full_cmd); + return output; + } + + static std::string get_version_from_commit(const VcpkgPaths& paths, + const std::string& commit_id, + const std::string& port_name) + { + const std::string cmd = Strings::format(R"(show %s:ports/%s/CONTROL)", commit_id, port_name); + auto output = run_git_command(paths, cmd); + + const auto version = Strings::find_at_most_one_enclosed(output.output, "Version: ", "\n"); + Checks::check_exit(VCPKG_LINE_INFO, version.has_value(), "CONTROL file does not have a 'Version' field"); + return version.get()->to_string(); + } + + static std::vector<PortControlVersion> read_versions_from_log(const VcpkgPaths& paths, const std::string& port_name) + { + const std::string cmd = + Strings::format(R"(log --format="%%H %%cd" --date=short --left-only -- ports/%s/.)", port_name); + auto output = run_git_command(paths, cmd); + + auto commits = Util::fmap( + Strings::split(output.output, "\n"), [](const std::string& line) -> auto { + auto parts = Strings::split(line, " "); + return std::make_pair(parts[0], parts[1]); + }); + + std::vector<PortControlVersion> ret; + std::string last_version; + for (auto&& commit_date_pair : commits) + { + const std::string version = get_version_from_commit(paths, commit_date_pair.first, port_name); + if (last_version != version) + { + ret.emplace_back(PortControlVersion{commit_date_pair.first, version, commit_date_pair.second}); + last_version = version; + } + } + return ret; + } + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("history <port>"), + 1, + 1, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + std::string port_name = args.command_arguments.at(0); + std::vector<PortControlVersion> versions = read_versions_from_log(paths, port_name); + System::print2(" version date vcpkg commit\n"); + for (auto&& version : versions) + { + System::printf("%20.20s %s %s\n", version.version, version.date, version.commit_id); + } + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.portsdiff.cpp b/toolsrc/src/vcpkg/commands.portsdiff.cpp index b30c38f43..cddc274b8 100644 --- a/toolsrc/src/vcpkg/commands.portsdiff.cpp +++ b/toolsrc/src/vcpkg/commands.portsdiff.cpp @@ -105,7 +105,7 @@ namespace vcpkg::Commands::PortsDiff std::map<std::string, VersionT> names_and_versions; for (auto&& port : all_ports) names_and_versions.emplace(port->core_paragraph->name, port->core_paragraph->version); - fs.remove_all(temp_checkout_path, ec); + fs.remove_all(temp_checkout_path, VCPKG_LINE_INFO); return names_and_versions; } diff --git a/toolsrc/src/vcpkg/commands.search.cpp b/toolsrc/src/vcpkg/commands.search.cpp index a0afd69e1..943233502 100644 --- a/toolsrc/src/vcpkg/commands.search.cpp +++ b/toolsrc/src/vcpkg/commands.search.cpp @@ -2,12 +2,15 @@ #include <vcpkg/base/system.print.h> #include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> #include <vcpkg/globalstate.h> #include <vcpkg/help.h> #include <vcpkg/paragraphs.h> #include <vcpkg/sourceparagraph.h> #include <vcpkg/vcpkglib.h> +using vcpkg::Dependencies::PathsPortFileProvider; + namespace vcpkg::Commands::Search { static constexpr StringLiteral OPTION_FULLDESC = @@ -63,7 +66,10 @@ namespace vcpkg::Commands::Search const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); const bool full_description = Util::Sets::contains(options.switches, OPTION_FULLDESC); - auto source_paragraphs = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); + PathsPortFileProvider provider(paths, args.overlay_ports.get()); + auto source_paragraphs = + Util::fmap(provider.load_all_control_files(), + [](auto&& port) -> const SourceControlFile* { return port->source_control_file.get(); }); if (args.command_arguments.empty()) { diff --git a/toolsrc/src/vcpkg/commands.upgrade.cpp b/toolsrc/src/vcpkg/commands.upgrade.cpp index 29815ca94..b1dbf6194 100644 --- a/toolsrc/src/vcpkg/commands.upgrade.cpp +++ b/toolsrc/src/vcpkg/commands.upgrade.cpp @@ -43,7 +43,8 @@ namespace vcpkg::Commands::Upgrade StatusParagraphs status_db = database_load_check(paths); - Dependencies::PathsPortFileProvider provider(paths); + // Load ports from ports dirs + Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); Dependencies::PackageGraph graph(provider, status_db); // input sanitization @@ -85,12 +86,12 @@ namespace vcpkg::Commands::Upgrade not_installed.push_back(spec); } - auto maybe_scf = provider.get_control_file(spec.name()); - if (auto p_scf = maybe_scf.get()) + auto maybe_scfl = provider.get_control_file(spec.name()); + if (auto p_scfl = maybe_scfl.get()) { if (it != status_db.end()) { - if (p_scf->core_paragraph->version != (*it)->package.version) + if (p_scfl->source_control_file->core_paragraph->version != (*it)->package.version) { to_upgrade.push_back(spec); } @@ -153,6 +154,7 @@ namespace vcpkg::Commands::Upgrade const Build::BuildPackageOptions install_plan_options = { Build::UseHeadVersion::NO, Build::AllowDownloads::YES, + Build::OnlyDownloads::NO, Build::CleanBuildtrees::NO, Build::CleanPackages::NO, Build::CleanDownloads::NO, @@ -170,7 +172,7 @@ namespace vcpkg::Commands::Upgrade } } - Dependencies::print_plan(plan, true); + Dependencies::print_plan(plan, true, paths.ports); if (!no_dry_run) { diff --git a/toolsrc/src/vcpkg/commands.xvsinstances.cpp b/toolsrc/src/vcpkg/commands.xvsinstances.cpp index 542ebd56c..a110bbb18 100644 --- a/toolsrc/src/vcpkg/commands.xvsinstances.cpp +++ b/toolsrc/src/vcpkg/commands.xvsinstances.cpp @@ -28,6 +28,7 @@ namespace vcpkg::Commands::X_VSInstances Checks::exit_success(VCPKG_LINE_INFO);
#else
+ Util::unused(args, paths);
Checks::exit_with_message(VCPKG_LINE_INFO, "This command is not supported on non-windows platforms.");
#endif
}
diff --git a/toolsrc/src/vcpkg/dependencies.cpp b/toolsrc/src/vcpkg/dependencies.cpp index f4a05e7dc..7c3f2df4c 100644 --- a/toolsrc/src/vcpkg/dependencies.cpp +++ b/toolsrc/src/vcpkg/dependencies.cpp @@ -13,45 +13,48 @@ namespace vcpkg::Dependencies { - struct ClusterInstalled + namespace { - InstalledPackageView ipv; - std::set<PackageSpec> remove_edges; - std::set<std::string> original_features; - }; + struct ClusterInstalled + { + 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; - }; + struct ClusterSource + { + const SourceControlFileLocation* scfl = nullptr; + std::unordered_map<std::string, std::vector<FeatureSpec>> build_edges; + }; - /// <summary> - /// Representation of a package and its features in a ClusterGraph. - /// </summary> - struct Cluster : Util::MoveOnlyBase - { - PackageSpec spec; + /// <summary> + /// Representation of a package and its features in a ClusterGraph. + /// </summary> + struct Cluster : Util::MoveOnlyBase + { + PackageSpec spec; - Optional<ClusterInstalled> installed; - Optional<ClusterSource> source; + 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; - bool minus = false; - bool transient_uninstalled = true; - RequestType request_type = RequestType::AUTO_SELECTED; - }; + // Note: this map can contain "special" strings such as "" and "*" + std::unordered_map<std::string, bool> plus; + std::set<std::string> to_install_features; + bool minus = false; + bool transient_uninstalled = true; + RequestType request_type = RequestType::AUTO_SELECTED; + }; - struct ClusterPtr - { - Cluster* ptr; + struct ClusterPtr + { + Cluster* ptr; - Cluster* operator->() const { return ptr; } - }; + Cluster* operator->() const { return ptr; } + }; - bool operator==(const ClusterPtr& l, const ClusterPtr& r) { return l.ptr == r.ptr; } + bool operator==(const ClusterPtr& l, const ClusterPtr& r) { return l.ptr == r.ptr; } + } } namespace std @@ -92,12 +95,12 @@ namespace vcpkg::Dependencies if (it == m_graph.end()) { // Load on-demand from m_provider - auto maybe_scf = m_provider.get_control_file(spec.name()); + auto maybe_scfl = m_provider.get_control_file(spec.name()); auto& clust = m_graph[spec]; clust.spec = spec; - if (auto p_scf = maybe_scf.get()) + if (auto p_scfl = maybe_scfl.get()) { - clust.source = cluster_from_scf(*p_scf, clust.spec.triplet()); + clust.source = cluster_from_scf(*p_scfl, clust.spec.triplet()); } return clust; } @@ -105,15 +108,16 @@ namespace vcpkg::Dependencies } private: - static ClusterSource cluster_from_scf(const SourceControlFile& scf, Triplet t) + static ClusterSource cluster_from_scf(const SourceControlFileLocation& scfl, Triplet t) { ClusterSource ret; - ret.build_edges.emplace("core", filter_dependencies_to_specs(scf.core_paragraph->depends, t)); + ret.build_edges.emplace("core", + filter_dependencies_to_specs(scfl.source_control_file->core_paragraph->depends, t)); - for (const auto& feature : scf.feature_paragraphs) + for (const auto& feature : scfl.source_control_file->feature_paragraphs) ret.build_edges.emplace(feature->name, filter_dependencies_to_specs(feature->depends, t)); - ret.scf = &scf; + ret.scfl = &scfl; return ret; } @@ -121,6 +125,28 @@ namespace vcpkg::Dependencies const PortFileProvider& m_provider; }; + static std::string to_output_string(RequestType request_type, + const CStringView s, + const Build::BuildPackageOptions& options, + const fs::path& install_port_path, + const fs::path& default_port_path) + { + if (!default_port_path.empty() && + !Strings::case_insensitive_ascii_starts_with(install_port_path.u8string(), default_port_path.u8string())) + { + const char* const from_head = options.use_head_version == Build::UseHeadVersion::YES ? " (from HEAD)" : ""; + switch (request_type) + { + case RequestType::AUTO_SELECTED: + return Strings::format(" * %s%s -- %s", s, from_head, install_port_path.u8string()); + case RequestType::USER_REQUESTED: + return Strings::format(" %s%s -- %s", s, from_head, install_port_path.u8string()); + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + return to_output_string(request_type, s, options); + } + std::string to_output_string(RequestType request_type, const CStringView s, const Build::BuildPackageOptions& options) @@ -151,12 +177,12 @@ namespace vcpkg::Dependencies } InstallPlanAction::InstallPlanAction(const PackageSpec& spec, - const SourceControlFile& scf, + const SourceControlFileLocation& scfl, const std::set<std::string>& features, const RequestType& request_type, std::vector<PackageSpec>&& dependencies) : spec(spec) - , source_control_file(scf) + , source_control_file_location(scfl) , plan_type(InstallPlanType::BUILD_AND_INSTALL) , request_type(request_type) , build_options{} @@ -268,37 +294,145 @@ namespace vcpkg::Dependencies return left->spec.name() < right->spec.name(); } - MapPortFileProvider::MapPortFileProvider(const std::unordered_map<std::string, SourceControlFile>& map) : ports(map) + MapPortFileProvider::MapPortFileProvider(const std::unordered_map<std::string, SourceControlFileLocation>& map) + : ports(map) { } - Optional<const SourceControlFile&> MapPortFileProvider::get_control_file(const std::string& spec) const + Optional<const SourceControlFileLocation&> MapPortFileProvider::get_control_file(const std::string& spec) const { auto scf = ports.find(spec); if (scf == ports.end()) return nullopt; return scf->second; } - PathsPortFileProvider::PathsPortFileProvider(const VcpkgPaths& paths) : ports(paths) {} + std::vector<const SourceControlFileLocation*> MapPortFileProvider::load_all_control_files() const + { + return Util::fmap(ports, [](auto&& kvpair) -> const SourceControlFileLocation* { return &kvpair.second; }); + } - Optional<const SourceControlFile&> PathsPortFileProvider::get_control_file(const std::string& spec) const + PathsPortFileProvider::PathsPortFileProvider(const vcpkg::VcpkgPaths& paths, + const std::vector<std::string>* ports_dirs_paths) + : filesystem(paths.get_filesystem()) + { + auto& fs = Files::get_real_filesystem(); + if (ports_dirs_paths) + { + for (auto&& overlay_path : *ports_dirs_paths) + { + if (!overlay_path.empty()) + { + auto overlay = fs::stdfs::canonical(fs::u8path(overlay_path)); + + Checks::check_exit(VCPKG_LINE_INFO, + filesystem.exists(overlay), + "Error: Path \"%s\" does not exist", + overlay.string()); + + Checks::check_exit(VCPKG_LINE_INFO, + fs::is_directory(fs.status(VCPKG_LINE_INFO, overlay)), + "Error: Path \"%s\" must be a directory", + overlay.string()); + + ports_dirs.emplace_back(overlay); + } + } + } + ports_dirs.emplace_back(paths.ports); + } + + Optional<const SourceControlFileLocation&> PathsPortFileProvider::get_control_file(const std::string& spec) const { auto cache_it = cache.find(spec); if (cache_it != cache.end()) { return cache_it->second; } - Parse::ParseExpected<SourceControlFile> source_control_file = - Paragraphs::try_load_port(ports.get_filesystem(), ports.port_dir(spec)); - if (auto scf = source_control_file.get()) + for (auto&& ports_dir : ports_dirs) { - auto it = cache.emplace(spec, std::move(*scf->get())); - return it.first->second; + // Try loading individual port + if (filesystem.exists(ports_dir / "CONTROL")) + { + auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); + if (auto scf = maybe_scf.get()) + { + if (scf->get()->core_paragraph->name == spec) + { + SourceControlFileLocation scfl{std::move(*scf), ports_dir}; + auto it = cache.emplace(spec, std::move(scfl)); + return it.first->second; + } + } + else + { + vcpkg::print_error_message(maybe_scf.error()); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Failed to load port from %s", spec, ports_dir.u8string()); + } + } + + auto found_scf = Paragraphs::try_load_port(filesystem, ports_dir / spec); + if (auto scf = found_scf.get()) + { + if (scf->get()->core_paragraph->name == spec) + { + SourceControlFileLocation scfl{std::move(*scf), ports_dir / spec}; + auto it = cache.emplace(spec, std::move(scfl)); + return it.first->second; + } + } } + return nullopt; } + std::vector<const SourceControlFileLocation*> PathsPortFileProvider::load_all_control_files() const + { + // Reload cache with ports contained in all ports_dirs + cache.clear(); + std::vector<const SourceControlFileLocation*> ret; + for (auto&& ports_dir : ports_dirs) + { + // Try loading individual port + if (filesystem.exists(ports_dir / "CONTROL")) + { + auto maybe_scf = Paragraphs::try_load_port(filesystem, ports_dir); + if (auto scf = maybe_scf.get()) + { + auto port_name = scf->get()->core_paragraph->name; + if (cache.find(port_name) == cache.end()) + { + SourceControlFileLocation scfl{std::move(*scf), ports_dir}; + auto it = cache.emplace(port_name, std::move(scfl)); + ret.emplace_back(&it.first->second); + } + } + else + { + vcpkg::print_error_message(maybe_scf.error()); + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Failed to load port from %s", ports_dir.u8string()); + } + continue; + } + + // Try loading all ports inside ports_dir + auto found_scf = Paragraphs::load_all_ports(filesystem, ports_dir); + for (auto&& scf : found_scf) + { + auto port_name = scf->core_paragraph->name; + if (cache.find(port_name) == cache.end()) + { + SourceControlFileLocation scfl{std::move(scf), ports_dir / port_name}; + auto it = cache.emplace(port_name, std::move(scfl)); + ret.emplace_back(&it.first->second); + } + } + } + return ret; + } + std::vector<RemovePlanAction> create_remove_plan(const std::vector<PackageSpec>& specs, const StatusParagraphs& status_db) { @@ -495,10 +629,11 @@ namespace vcpkg::Dependencies VCPKG_LINE_INFO, "Error: Cannot find definition for package `%s`.", cluster.spec.name()); } + auto&& control_file = *p_source->scfl->source_control_file.get(); if (feature.empty()) { // Add default features for this package. This is an exact reference, so ignore prevent_default_features. - for (auto&& default_feature : p_source->scf->core_paragraph.get()->default_features) + for (auto&& default_feature : control_file.core_paragraph.get()->default_features) { auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); if (res != MarkPlusResult::SUCCESS) @@ -513,7 +648,7 @@ namespace vcpkg::Dependencies if (feature == "*") { - for (auto&& fpgh : p_source->scf->feature_paragraphs) + for (auto&& fpgh : control_file.feature_paragraphs) { auto res = mark_plus(fpgh->name, cluster, graph, graph_plan, prevent_default_features); @@ -538,7 +673,8 @@ namespace vcpkg::Dependencies } } - // This feature was or will be uninstalled, therefore we need to rebuild + // The feature was not previously installed. Mark the cluster + // (aka the entire port) to be removed before re-adding it. mark_minus(cluster, graph, graph_plan, prevent_default_features); return follow_plus_dependencies(feature, cluster, graph, graph_plan, prevent_default_features); @@ -590,7 +726,8 @@ namespace vcpkg::Dependencies // 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) + auto&& control_file = *p_source->scfl->source_control_file.get(); + for (auto&& default_feature : control_file.core_paragraph->default_features) { if (std::find(previous_df.begin(), previous_df.end(), default_feature) == previous_df.end()) { @@ -635,9 +772,10 @@ namespace vcpkg::Dependencies /// <param name="map">Map of all source control files in the current environment.</param> /// <param name="specs">Feature specifications to resolve dependencies for.</param> /// <param name="status_db">Status of installed packages in the current environment.</param> - std::vector<AnyAction> create_feature_install_plan(const std::unordered_map<std::string, SourceControlFile>& map, - const std::vector<FeatureSpec>& specs, - const StatusParagraphs& status_db) + std::vector<AnyAction> create_feature_install_plan( + const std::unordered_map<std::string, SourceControlFileLocation>& map, + const std::vector<FeatureSpec>& specs, + const StatusParagraphs& status_db) { MapPortFileProvider provider(map); return create_feature_install_plan(provider, specs, status_db); @@ -698,7 +836,10 @@ 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.value_or_exit(VCPKG_LINE_INFO).scf; + auto* pscfl = p_cluster->source.value_or_exit(VCPKG_LINE_INFO).scfl; + Checks::check_exit( + VCPKG_LINE_INFO, pscfl != nullptr, "Error: Expected a SourceControlFileLocation to exist"); + auto&& scfl = *pscfl; auto dep_specs = Util::fmap(m_graph_plan->install_graph.adjacency_list(p_cluster), [](ClusterPtr const& p) { return p->spec; }); @@ -706,7 +847,7 @@ namespace vcpkg::Dependencies plan.emplace_back(InstallPlanAction{ p_cluster->spec, - *pscf, + scfl, p_cluster->to_install_features, p_cluster->request_type, std::move(dep_specs), @@ -777,7 +918,9 @@ namespace vcpkg::Dependencies PackageGraph::~PackageGraph() = default; - void print_plan(const std::vector<AnyAction>& action_plan, const bool is_recursive) + void print_plan(const std::vector<AnyAction>& action_plan, + const bool is_recursive, + const fs::path& default_ports_dir) { std::vector<const RemovePlanAction*> remove_plans; std::vector<const InstallPlanAction*> rebuilt_plans; @@ -832,8 +975,14 @@ namespace vcpkg::Dependencies std::sort(already_installed_plans.begin(), already_installed_plans.end(), &InstallPlanAction::compare_by_name); std::sort(excluded.begin(), excluded.end(), &InstallPlanAction::compare_by_name); - static auto actions_to_output_string = [](const std::vector<const InstallPlanAction*>& v) { - return Strings::join("\n", v, [](const InstallPlanAction* p) { + static auto actions_to_output_string = [&](const std::vector<const InstallPlanAction*>& v) { + return Strings::join("\n", v, [&](const InstallPlanAction* p) { + if (auto* pscfl = p->source_control_file_location.get()) + { + return to_output_string( + p->request_type, p->displayname(), p->build_options, pscfl->source_location, default_ports_dir); + } + return to_output_string(p->request_type, p->displayname(), p->build_options); }); }; diff --git a/toolsrc/src/vcpkg/export.cpp b/toolsrc/src/vcpkg/export.cpp index 607066164..34f9053ea 100644 --- a/toolsrc/src/vcpkg/export.cpp +++ b/toolsrc/src/vcpkg/export.cpp @@ -74,6 +74,7 @@ namespace vcpkg::Export static constexpr Build::BuildPackageOptions BUILD_OPTIONS = { Build::UseHeadVersion::NO, Build::AllowDownloads::YES, + Build::OnlyDownloads::NO, Build::CleanBuildtrees::NO, Build::CleanPackages::NO, Build::CleanDownloads::NO, @@ -349,26 +350,33 @@ namespace vcpkg::Export struct OptionPair { - const std::string& name; + const StringLiteral& name; Optional<std::string>& out_opt; }; - const auto options_implies = - [&](const std::string& main_opt_name, bool main_opt, Span<const OptionPair> implying_opts) { - if (main_opt) - { - for (auto&& opt : implying_opts) - opt.out_opt = maybe_lookup(options.settings, opt.name); - } - else - { - for (auto&& opt : implying_opts) - Checks::check_exit(VCPKG_LINE_INFO, - !maybe_lookup(options.settings, opt.name), - "%s is only valid with %s", - opt.name, - main_opt_name); - } - }; + const auto options_implies = [&](const StringLiteral& main_opt_name, + bool is_main_opt, + const std::initializer_list<OptionPair>& implying_opts) { + if (is_main_opt) + { + for (auto&& opt : implying_opts) + opt.out_opt = maybe_lookup(options.settings, opt.name); + } + else + { + for (auto&& opt : implying_opts) + Checks::check_exit(VCPKG_LINE_INFO, + !maybe_lookup(options.settings, opt.name), + "%s is only valid with %s", + opt.name, + main_opt_name); + } + }; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// there's a bug in VS 2015 that causes a bunch of "unreferenced local variable" warnings +#pragma warning(push) +#pragma warning(disable : 4189) +#endif options_implies(OPTION_NUGET, ret.nuget, @@ -393,6 +401,10 @@ namespace vcpkg::Export {OPTION_CHOCOLATEY_MAINTAINER, ret.chocolatey_options.maybe_maintainer}, {OPTION_CHOCOLATEY_VERSION_SUFFIX, ret.chocolatey_options.maybe_version_suffix}, }); + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +#pragma warning(pop) +#endif return ret; } @@ -417,8 +429,10 @@ namespace vcpkg::Export Files::Filesystem& fs = paths.get_filesystem(); const fs::path export_to_path = paths.root; const fs::path raw_exported_dir_path = export_to_path / export_id; + fs.remove_all(raw_exported_dir_path, VCPKG_LINE_INFO); + + // TODO: error handling std::error_code ec; - fs.remove_all(raw_exported_dir_path, ec); fs.create_directory(raw_exported_dir_path, ec); // execute the plan @@ -493,7 +507,7 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console if (!opts.raw) { - fs.remove_all(raw_exported_dir_path, ec); + fs.remove_all(raw_exported_dir_path, VCPKG_LINE_INFO); } } @@ -505,7 +519,10 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console // create the plan const StatusParagraphs status_db = database_load_check(paths); - Dependencies::PathsPortFileProvider provider(paths); + + // Load ports from ports dirs + Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); + std::vector<ExportPlanAction> export_plan = Dependencies::create_export_plan(opts.specs, status_db); Checks::check_exit(VCPKG_LINE_INFO, !export_plan.empty(), "Export plan cannot be empty"); diff --git a/toolsrc/src/vcpkg/help.cpp b/toolsrc/src/vcpkg/help.cpp index b92e9ca59..0c53536fb 100644 --- a/toolsrc/src/vcpkg/help.cpp +++ b/toolsrc/src/vcpkg/help.cpp @@ -91,6 +91,7 @@ namespace vcpkg::Help " vcpkg list List installed packages\n" " vcpkg update Display list of packages for updating\n" " vcpkg upgrade Rebuild all outdated packages\n" + " vcpkg x-history <pkg> Shows the history of CONTROL versions of a package\n" " vcpkg hash <file> [alg] Hash a file by specific algorithm, default SHA512\n" " vcpkg help topics Display the list of help topics\n" " vcpkg help <topic> Display help for a specific topic\n" @@ -111,15 +112,21 @@ namespace vcpkg::Help " vcpkg contact Display contact information to send feedback\n" "\n" "Options:\n" - " --triplet <t> Specify the target architecture triplet.\n" + " --triplet <t> Specify the target architecture triplet\n" " (default: " ENVVAR(VCPKG_DEFAULT_TRIPLET) // ", see 'vcpkg help triplet')\n" "\n" + " --overlay-ports=<path> Specify directories to be used when searching for ports\n" + "\n" + " --overlay-triplets=<path> Specify directories containing triplets files\n" + "\n" " --vcpkg-root <path> Specify the vcpkg root " "directory\n" " (default: " ENVVAR(VCPKG_ROOT) // ")\n" "\n" + " --x-scripts-root=<path> (Experimental) Specify the scripts root directory\n" + "\n" " @response_file Specify a " "response file to provide additional parameters\n" "\n" diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index 88b15fe7a..21be2d7b0 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -174,7 +174,7 @@ namespace vcpkg::Install const std::vector<fs::path> package_file_paths = fs.get_files_recursive(package_dir); const size_t package_remove_char_count = package_dir.generic_string().size() + 1; // +1 for the slash auto package_files = Util::fmap(package_file_paths, [package_remove_char_count](const fs::path& path) { - return std::move(std::string(path.generic_string(), package_remove_char_count)); + return std::string(path.generic_string(), package_remove_char_count); }); return SortedVector<std::string>(std::move(package_files)); @@ -202,6 +202,12 @@ namespace vcpkg::Install struct intersection_compare { + // The VS2015 standard library requires comparison operators of T and U + // to also support comparison of T and T, and of U and U, due to debug checks. +#if _MSC_VER <= 1910 + bool operator()(const std::string& lhs, const std::string& rhs) { return lhs < rhs; } + bool operator()(const file_pack& lhs, const file_pack& rhs) { return lhs.first < rhs.first; } +#endif bool operator()(const std::string& lhs, const file_pack& rhs) { return lhs < rhs.first; } bool operator()(const file_pack& lhs, const std::string& rhs) { return lhs.first < rhs; } }; @@ -330,14 +336,19 @@ namespace vcpkg::Install System::printf("Building package %s...\n", display_name_with_features); auto result = [&]() -> Build::ExtendedBuildResult { - const Build::BuildPackageConfig build_config{action.source_control_file.value_or_exit(VCPKG_LINE_INFO), - action.spec.triplet(), - paths.port_dir(action.spec), - action.build_options, - action.feature_list}; + const auto& scfl = action.source_control_file_location.value_or_exit(VCPKG_LINE_INFO); + const Build::BuildPackageConfig build_config{ + scfl, action.spec.triplet(), action.build_options, action.feature_list}; return Build::build_package(paths, build_config, status_db); }(); + if (BuildResult::DOWNLOADED == result.code) + { + System::print2( + System::Color::success, "Downloaded sources for package ", display_name_with_features, "\n"); + return result; + } + if (result.code != Build::BuildResult::SUCCEEDED) { System::print2(System::Color::error, Build::create_error_message(result.code, action.spec), "\n"); @@ -354,8 +365,7 @@ namespace vcpkg::Install { auto& fs = paths.get_filesystem(); const fs::path package_dir = paths.package_dir(action.spec); - std::error_code ec; - fs.remove_all(package_dir, ec); + fs.remove_all(package_dir, VCPKG_LINE_INFO); } if (action.build_options.clean_downloads == Build::CleanDownloads::YES) @@ -464,16 +474,18 @@ namespace vcpkg::Install static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; static constexpr StringLiteral OPTION_USE_HEAD_VERSION = "--head"; static constexpr StringLiteral OPTION_NO_DOWNLOADS = "--no-downloads"; + static constexpr StringLiteral OPTION_ONLY_DOWNLOADS = "--only-downloads"; 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 StringLiteral OPTION_CLEAN_AFTER_BUILD = "--clean-after-build"; - static constexpr std::array<CommandSwitch, 7> INSTALL_SWITCHES = {{ + static constexpr std::array<CommandSwitch, 8> 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_ONLY_DOWNLOADS, "Download sources but don't build packages"}, {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"}, @@ -628,10 +640,12 @@ namespace vcpkg::Install const bool dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); 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 only_downloads = Util::Sets::contains(options.switches, (OPTION_ONLY_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 bool clean_after_build = Util::Sets::contains(options.switches, (OPTION_CLEAN_AFTER_BUILD)); - const KeepGoing keep_going = to_keep_going(Util::Sets::contains(options.switches, OPTION_KEEP_GOING)); + const KeepGoing keep_going = + to_keep_going(Util::Sets::contains(options.switches, OPTION_KEEP_GOING) || only_downloads); auto& fs = paths.get_filesystem(); @@ -644,21 +658,19 @@ namespace vcpkg::Install const Build::BuildPackageOptions install_plan_options = { Util::Enum::to_enum<Build::UseHeadVersion>(use_head_version), Util::Enum::to_enum<Build::AllowDownloads>(!no_downloads), + Util::Enum::to_enum<Build::OnlyDownloads>(only_downloads), clean_after_build ? Build::CleanBuildtrees::YES : Build::CleanBuildtrees::NO, clean_after_build ? Build::CleanPackages::YES : Build::CleanPackages::NO, clean_after_build ? Build::CleanDownloads::YES : Build::CleanDownloads::NO, download_tool, - GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, + (GlobalState::g_binary_caching && !only_downloads) ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, Build::FailOnTombstone::NO, }; - auto all_ports = Paragraphs::load_all_ports(fs, paths.ports); - std::unordered_map<std::string, SourceControlFile> scf_map; - for (auto&& port : all_ports) - scf_map[port->core_paragraph->name] = std::move(*port); - MapPortFileProvider provider(scf_map); + //// Load ports from ports dirs + PathsPortFileProvider provider(paths, args.overlay_ports.get()); - // Note: action_plan will hold raw pointers to SourceControlFiles from this map + // Note: action_plan will hold raw pointers to SourceControlFileLocations from this map std::vector<AnyAction> action_plan = create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db); @@ -686,7 +698,7 @@ namespace vcpkg::Install Metrics::g_metrics.lock()->track_property("installplan", specs_string); - Dependencies::print_plan(action_plan, is_recursive); + Dependencies::print_plan(action_plan, is_recursive, paths.ports); if (dry_run) { diff --git a/toolsrc/src/vcpkg/logicexpression.cpp b/toolsrc/src/vcpkg/logicexpression.cpp new file mode 100644 index 000000000..ccb8b00c4 --- /dev/null +++ b/toolsrc/src/vcpkg/logicexpression.cpp @@ -0,0 +1,262 @@ + +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/system.print.h> +#include <vcpkg/logicexpression.h> + +#include <string> +#include <vector> + +namespace vcpkg +{ + struct ParseError + { + ParseError(int column, std::string line, std::string message) : column(column), line(line), message(message) {} + + const int column; + const std::string line; + const std::string message; + + void print_error() const + { + System::print2(System::Color::error, + "Error: ", + message, + "\n" + " on expression: \"", + line, + "\"\n", + " ", + std::string(column, ' '), + "^\n"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + }; + + // logic expression supports the following : + // primary-expression: + // ( logic-expression ) + // identifier + // identifier: + // alpha-numeric string of characters + // logic-expression: <- this is the entry point + // not-expression + // not-expression | logic-expression + // not-expression & logic-expression + // not-expression: + // ! primary-expression + // primary-expression + // + // | and & have equal precidence and cannot be used together at the same nesting level + // for example a|b&c is not allowd but (a|b)&c and a|(b&c) are allowed. + class ExpressionParser + { + public: + ExpressionParser(const std::string& str, const std::string& evaluation_context) + : raw_text(str) + , evaluation_context(evaluation_context) + , current_iter(raw_text.begin()) + , current_char(get_current_char()) + { + skip_whitespace(); + + final_result = logic_expression(); + + if (current_iter != raw_text.end()) + { + add_error("Invalid logic expression"); + } + + if (err) + { + err->print_error(); + final_result = false; + } + } + + bool get_result() const { return final_result; } + + bool has_error() const { return err == nullptr; } + + private: + const std::string& raw_text; + const std::string& evaluation_context; + std::string::const_iterator current_iter; + char current_char; + + bool final_result; + + std::unique_ptr<ParseError> err; + + char get_current_char() const { return (current_iter != raw_text.end() ? *current_iter : '\0'); } + + void add_error(std::string message, int column = -1) + { + // avoid castcading errors by only saving the first + if (!err) + { + if (column < 0) + { + column = current_column(); + } + err = std::make_unique<ParseError>(column, raw_text, message); + } + + // Avoid error loops by skipping to the end + skip_to_end(); + } + + int current_column() const { return static_cast<int>(current_iter - raw_text.begin()); } + + void skip_to_end() + { + current_iter = raw_text.end(); + current_char = '\0'; + } + char current() const { return current_char; } + char next() + { + if (current_char != '\0') + { + current_iter++; + current_char = get_current_char(); + } + return current(); + } + void skip_whitespace() + { + while (current_char == ' ' || current_char == '\t') + { + current_char = next(); + } + } + char next_skip_whitespace() + { + next(); + skip_whitespace(); + return current_char; + } + + static bool is_alphanum(char ch) + { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || (ch == '-'); + } + + bool evaluate_identifier(const std::string name) const + { + return evaluation_context.find(name) != std::string::npos; + } + + // identifier: + // alpha-numeric string of characters + bool identifier_expression() + { + auto curr = current(); + std::string name; + + for (curr = current(); is_alphanum(curr); curr = next()) + { + name += curr; + } + + if (name.empty()) + { + add_error("Invalid logic expression, unexpected character"); + return false; + } + + bool result = evaluate_identifier(name); + skip_whitespace(); + return result; + } + + // not-expression: + // ! primary-expression + // primary-expression + bool not_expression() + { + if (current() == '!') + { + next_skip_whitespace(); + return !primary_expression(); + } + + return primary_expression(); + } + + template<char oper, char other, bool operation(bool, bool)> + bool logic_expression_helper(bool seed) + { + do + { + // Support chains of the operator to avoid breaking backwards compatability + while (next() == oper) + { + }; + seed = operation(not_expression(), seed); + + } while (current() == oper); + + if (current() == other) + { + add_error("Mixing & and | is not allowed, Use () to specify order of operations."); + } + + skip_whitespace(); + return seed; + } + static bool and_helper(bool left, bool right) { return left && right; } + static bool or_helper(bool left, bool right) { return left || right; } + + // logic-expression: <- entry point + // not-expression + // not-expression | logic-expression + // not-expression & logic-expression + bool logic_expression() + { + auto result = not_expression(); + + switch (current()) + { + case '|': + { + return logic_expression_helper<'|', '&', or_helper>(result); + } + case '&': + { + return logic_expression_helper<'&', '|', and_helper>(result); + } + default: return result; + } + } + + // primary-expression: + // ( logic-expression ) + // identifier + bool primary_expression() + { + if (current() == '(') + { + next_skip_whitespace(); + bool result = logic_expression(); + if (current() != ')') + { + add_error("Error: missing closing )"); + return result; + } + next_skip_whitespace(); + return result; + } + + return identifier_expression(); + } + }; + + bool evaluate_expression(const std::string& expression, const std::string& evaluation_context) + { + ExpressionParser parser(expression, evaluation_context); + + return parser.get_result(); + } +} diff --git a/toolsrc/src/vcpkg/metrics.cpp b/toolsrc/src/vcpkg/metrics.cpp index 9dd520ed6..b8c55919e 100644 --- a/toolsrc/src/vcpkg/metrics.cpp +++ b/toolsrc/src/vcpkg/metrics.cpp @@ -266,7 +266,7 @@ namespace vcpkg::Metrics const auto match = *next; if (match[0] != "00-00-00-00-00-00") { - return vcpkg::Hash::get_string_hash(match[0], "SHA256"); + return vcpkg::Hash::get_string_hash(match[0].str(), Hash::Algorithm::Sha256); } ++next; } @@ -307,7 +307,9 @@ namespace vcpkg::Metrics void Metrics::upload(const std::string& payload) { -#if defined(_WIN32) +#if !defined(_WIN32) + Util::unused(payload); +#else HINTERNET connect = nullptr, request = nullptr; BOOL results = FALSE; diff --git a/toolsrc/src/vcpkg/paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index 21ef2c4d9..9cb7caff1 100644 --- a/toolsrc/src/vcpkg/paragraphs.cpp +++ b/toolsrc/src/vcpkg/paragraphs.cpp @@ -116,7 +116,7 @@ namespace vcpkg::Paragraphs skip_spaces(ch); } - void get_paragraph(char& ch, std::unordered_map<std::string, std::string>& fields) + void get_paragraph(char& ch, RawParagraph& fields) { fields.clear(); std::string fieldname; @@ -141,9 +141,9 @@ namespace vcpkg::Paragraphs } public: - std::vector<std::unordered_map<std::string, std::string>> get_paragraphs() + std::vector<RawParagraph> get_paragraphs() { - std::vector<std::unordered_map<std::string, std::string>> paragraphs; + std::vector<RawParagraph> paragraphs; char ch; peek(ch); @@ -164,51 +164,48 @@ namespace vcpkg::Paragraphs } }; - Expected<std::unordered_map<std::string, std::string>> get_single_paragraph(const Files::Filesystem& fs, - const fs::path& control_path) + static Expected<RawParagraph> parse_single_paragraph(const std::string& str) { - const Expected<std::string> contents = fs.read_contents(control_path); - if (auto spgh = contents.get()) + const std::vector<RawParagraph> p = Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); + + if (p.size() == 1) { - return parse_single_paragraph(*spgh); + return p.at(0); } - return contents.error(); + return std::error_code(ParagraphParseResult::EXPECTED_ONE_PARAGRAPH); } - Expected<std::vector<std::unordered_map<std::string, std::string>>> get_paragraphs(const Files::Filesystem& fs, - const fs::path& control_path) + Expected<RawParagraph> get_single_paragraph(const Files::Filesystem& fs, const fs::path& control_path) { const Expected<std::string> contents = fs.read_contents(control_path); if (auto spgh = contents.get()) { - return parse_paragraphs(*spgh); + return parse_single_paragraph(*spgh); } return contents.error(); } - Expected<std::unordered_map<std::string, std::string>> parse_single_paragraph(const std::string& str) + Expected<std::vector<RawParagraph>> get_paragraphs(const Files::Filesystem& fs, const fs::path& control_path) { - const std::vector<std::unordered_map<std::string, std::string>> p = - Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); - - if (p.size() == 1) + const Expected<std::string> contents = fs.read_contents(control_path); + if (auto spgh = contents.get()) { - return p.at(0); + return parse_paragraphs(*spgh); } - return std::error_code(ParagraphParseResult::EXPECTED_ONE_PARAGRAPH); + return contents.error(); } - Expected<std::vector<std::unordered_map<std::string, std::string>>> parse_paragraphs(const std::string& str) + Expected<std::vector<RawParagraph>> parse_paragraphs(const std::string& str) { return Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); } ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path) { - Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = get_paragraphs(fs, path / "CONTROL"); + Expected<std::vector<RawParagraph>> pghs = get_paragraphs(fs, path / "CONTROL"); if (auto vector_pghs = pghs.get()) { return SourceControlFile::parse_control_file(std::move(*vector_pghs)); @@ -221,7 +218,7 @@ namespace vcpkg::Paragraphs Expected<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec) { - Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = + Expected<std::vector<RawParagraph>> pghs = get_paragraphs(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL"); if (auto p = pghs.get()) diff --git a/toolsrc/src/vcpkg/parse.cpp b/toolsrc/src/vcpkg/parse.cpp index d50296cf8..6015d9927 100644 --- a/toolsrc/src/vcpkg/parse.cpp +++ b/toolsrc/src/vcpkg/parse.cpp @@ -2,12 +2,12 @@ #include <vcpkg/parse.h> +#include <vcpkg/base/system.print.h> #include <vcpkg/base/util.h> namespace vcpkg::Parse { - static Optional<std::string> remove_field(std::unordered_map<std::string, std::string>* fields, - const std::string& fieldname) + static Optional<std::string> remove_field(RawParagraph* fields, const std::string& fieldname) { auto it = fields->find(fieldname); if (it == fields->end()) @@ -45,6 +45,8 @@ namespace vcpkg::Parse return nullptr; } + static bool is_whitespace(char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; } + std::vector<std::string> parse_comma_list(const std::string& str) { if (str.empty()) @@ -54,26 +56,93 @@ namespace vcpkg::Parse std::vector<std::string> out; - size_t cur = 0; + auto iter = str.cbegin(); + do { - auto pos = str.find(',', cur); - if (pos == std::string::npos) + // Trim leading whitespace of each element + while (iter != str.cend() && is_whitespace(*iter)) + { + ++iter; + } + + // Allow commas inside of []. + bool bracket_nesting = false; + + auto element_begin = iter; + auto element_end = iter; + while (iter != str.cend() && (*iter != ',' || bracket_nesting)) + { + char value = *iter; + + // do not support nested [] + if (value == '[') + { + if (bracket_nesting) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Lists do not support nested brackets, Did you forget a ']'?\n" + "> '%s'\n" + "> %s^\n", + str, + std::string(static_cast<int>(iter - str.cbegin()), ' ')); + } + bracket_nesting = true; + } + else if (value == ']') + { + if (!bracket_nesting) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Found unmatched ']'. Did you forget a '['?\n" + "> '%s'\n" + "> %s^\n", + str, + std::string(static_cast<int>(iter - str.cbegin()), ' ')); + } + bracket_nesting = false; + } + + ++iter; + + // Trim ending whitespace + if (!is_whitespace(value)) + { + // Update element_end after iter is incremented so it will be one past. + element_end = iter; + } + } + + if (element_begin == element_end) { - out.push_back(str.substr(cur)); - break; + Checks::exit_with_message(VCPKG_LINE_INFO, + "Empty element in list\n" + "> '%s'\n" + "> %s^\n", + str, + std::string(static_cast<int>(element_begin - str.cbegin()), ' ')); } - out.push_back(str.substr(cur, pos - cur)); + out.push_back({element_begin, element_end}); - // skip comma and space - ++pos; - if (str[pos] == ' ') + if (iter != str.cend()) { - ++pos; + Checks::check_exit(VCPKG_LINE_INFO, *iter == ',', "Internal parsing error - expected comma"); + + // Not at the end, must be at a comma that needs to be stepped over + ++iter; + + if (iter == str.end()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Empty element in list\n" + "> '%s'\n" + "> %s^\n", + str, + std::string(str.length(), ' ')); + } } - cur = pos; - } while (cur != std::string::npos); + } while (iter != str.cend()); return out; } diff --git a/toolsrc/src/vcpkg/postbuildlint.cpp b/toolsrc/src/vcpkg/postbuildlint.cpp index d6581c2b4..a85d879fe 100644 --- a/toolsrc/src/vcpkg/postbuildlint.cpp +++ b/toolsrc/src/vcpkg/postbuildlint.cpp @@ -39,7 +39,7 @@ namespace vcpkg::PostBuildLint } }; - Span<const OutdatedDynamicCrt> get_outdated_dynamic_crts(const Optional<std::string>& toolset_version) + static Span<const OutdatedDynamicCrt> get_outdated_dynamic_crts(const Optional<std::string>& toolset_version) { static const std::vector<OutdatedDynamicCrt> V_NO_120 = { {"msvcp100.dll", R"(msvcp100\.dll)"}, @@ -462,7 +462,7 @@ namespace vcpkg::PostBuildLint return LintStatus::ERROR_DETECTED; } #endif - + Util::unused(expected_architecture, files); return LintStatus::SUCCESS; } @@ -857,14 +857,15 @@ namespace vcpkg::PostBuildLint size_t perform_all_checks(const PackageSpec& spec, const VcpkgPaths& paths, const PreBuildInfo& pre_build_info, - const BuildInfo& build_info) + const BuildInfo& build_info, + const fs::path& port_dir) { System::print2("-- Performing post-build validation\n"); const size_t error_count = perform_all_checks_and_return_error_count(spec, paths, pre_build_info, build_info); if (error_count != 0) { - const fs::path portfile = paths.ports / spec.name() / "portfile.cmake"; + const fs::path portfile = port_dir / "portfile.cmake"; System::print2(System::Color::error, "Found ", error_count, diff --git a/toolsrc/src/vcpkg/remove.cpp b/toolsrc/src/vcpkg/remove.cpp index f997667ac..65e00668a 100644 --- a/toolsrc/src/vcpkg/remove.cpp +++ b/toolsrc/src/vcpkg/remove.cpp @@ -73,7 +73,7 @@ namespace vcpkg::Remove if (ec) { #if defined(_WIN32) - fs::stdfs::permissions(target, fs::stdfs::perms::owner_all | fs::stdfs::perms::group_all, ec); + fs::stdfs::permissions(target, fs::perms::owner_all | fs::perms::group_all, ec); fs.remove(target, ec); if (ec) { @@ -86,7 +86,7 @@ namespace vcpkg::Remove #endif } } - else if (!fs::stdfs::exists(status)) + else if (!fs::exists(status)) { System::printf(System::Color::warning, "Warning: %s: file not found\n", target.u8string()); } @@ -179,8 +179,7 @@ namespace vcpkg::Remove { System::printf("Purging package %s...\n", display_name); Files::Filesystem& fs = paths.get_filesystem(); - std::error_code ec; - fs.remove_all(paths.packages / action.spec.dir(), ec); + fs.remove_all(paths.packages / action.spec.dir(), VCPKG_LINE_INFO); System::printf(System::Color::success, "Purging package %s... done\n", display_name); } } @@ -229,7 +228,8 @@ namespace vcpkg::Remove Checks::exit_fail(VCPKG_LINE_INFO); } - Dependencies::PathsPortFileProvider provider(paths); + // Load ports from ports dirs + Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); specs = Util::fmap(Update::find_outdated_packages(provider, status_db), [](auto&& outdated) { return outdated.spec; }); diff --git a/toolsrc/src/vcpkg/sourceparagraph.cpp b/toolsrc/src/vcpkg/sourceparagraph.cpp index 9bc59cbe7..ebb9cd4f4 100644 --- a/toolsrc/src/vcpkg/sourceparagraph.cpp +++ b/toolsrc/src/vcpkg/sourceparagraph.cpp @@ -1,5 +1,6 @@ #include "pch.h" +#include <vcpkg/logicexpression.h> #include <vcpkg/packagespec.h> #include <vcpkg/sourceparagraph.h> #include <vcpkg/triplet.h> @@ -25,6 +26,7 @@ namespace vcpkg static const std::string SUPPORTS = "Supports"; static const std::string VERSION = "Version"; static const std::string HOMEPAGE = "Homepage"; + static const std::string TYPE = "Type"; } static Span<const std::string> get_list_of_valid_fields() @@ -36,6 +38,7 @@ namespace vcpkg SourceParagraphFields::MAINTAINER, SourceParagraphFields::BUILD_DEPENDS, SourceParagraphFields::HOMEPAGE, + SourceParagraphFields::TYPE, }; return valid_fields; @@ -142,7 +145,7 @@ namespace vcpkg } ParseExpected<SourceControlFile> SourceControlFile::parse_control_file( - std::vector<std::unordered_map<std::string, std::string>>&& control_paragraphs) + std::vector<Parse::RawParagraph>&& control_paragraphs) { if (control_paragraphs.size() == 0) { @@ -222,15 +225,8 @@ namespace vcpkg std::vector<std::string> ret; for (auto&& dep : deps) { - auto qualifiers = Strings::split(dep.qualifier, "&"); - if (std::all_of(qualifiers.begin(), qualifiers.end(), [&](const std::string& qualifier) { - if (qualifier.empty()) return true; - if (qualifier[0] == '!') - { - return t.canonical_name().find(qualifier.substr(1)) == std::string::npos; - } - return t.canonical_name().find(qualifier) != std::string::npos; - })) + const auto& qualifier = dep.qualifier; + if (qualifier.empty() || evaluate_expression(qualifier, t.canonical_name())) { ret.emplace_back(dep.name()); } @@ -238,6 +234,20 @@ namespace vcpkg return ret; } + std::vector<Features> filter_dependencies_to_features(const std::vector<vcpkg::Dependency>& deps, const Triplet& t) + { + std::vector<Features> ret; + for (auto&& dep : deps) + { + const auto& qualifier = dep.qualifier; + if (qualifier.empty() || evaluate_expression(qualifier, t.canonical_name())) + { + ret.emplace_back(dep.depend); + } + } + return ret; + } + std::vector<FeatureSpec> filter_dependencies_to_specs(const std::vector<Dependency>& deps, const Triplet& t) { return FeatureSpec::from_strings_and_triplet(filter_dependencies(deps, t), t); diff --git a/toolsrc/src/vcpkg/statusparagraph.cpp b/toolsrc/src/vcpkg/statusparagraph.cpp index 86946a31a..f7e00f21c 100644 --- a/toolsrc/src/vcpkg/statusparagraph.cpp +++ b/toolsrc/src/vcpkg/statusparagraph.cpp @@ -24,7 +24,7 @@ namespace vcpkg .push_back('\n'); } - StatusParagraph::StatusParagraph(std::unordered_map<std::string, std::string>&& fields) + StatusParagraph::StatusParagraph(Parse::RawParagraph&& fields) : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) { auto status_it = fields.find(BinaryParagraphRequiredField::STATUS); diff --git a/toolsrc/src/vcpkg/statusparagraphs.cpp b/toolsrc/src/vcpkg/statusparagraphs.cpp index c642af59b..3c81728bb 100644 --- a/toolsrc/src/vcpkg/statusparagraphs.cpp +++ b/toolsrc/src/vcpkg/statusparagraphs.cpp @@ -7,8 +7,9 @@ namespace vcpkg { StatusParagraphs::StatusParagraphs() = default; - StatusParagraphs::StatusParagraphs(std::vector<std::unique_ptr<StatusParagraph>>&& ps) - : paragraphs(std::move(ps)){}; + StatusParagraphs::StatusParagraphs(std::vector<std::unique_ptr<StatusParagraph>>&& ps) : paragraphs(std::move(ps)) + { + } std::vector<std::unique_ptr<StatusParagraph>*> StatusParagraphs::find_all(const std::string& name, const Triplet& triplet) diff --git a/toolsrc/src/vcpkg/tools.cpp b/toolsrc/src/vcpkg/tools.cpp index 4f4b23055..9354493bd 100644 --- a/toolsrc/src/vcpkg/tools.cpp +++ b/toolsrc/src/vcpkg/tools.cpp @@ -274,6 +274,9 @@ namespace vcpkg const auto& program_files_32_bit = System::get_program_files_32_bit(); if (const auto pf = program_files_32_bit.get()) out_candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); +#else + // TODO: figure out if this should do anything on non-Windows + Util::unused(out_candidate_paths); #endif } virtual Optional<std::string> get_version(const fs::path& path_to_exe) const override @@ -294,20 +297,6 @@ CMake suite maintained and supported by Kitware (kitware.com/cmake). } }; - static fs::path get_7za_path(const VcpkgPaths& paths) - { -#if defined(_WIN32) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "7zip").value_or_exit(VCPKG_LINE_INFO); - 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 - } - struct NinjaProvider : ToolProvider { std::string m_exe = "ninja"; @@ -376,6 +365,9 @@ Type 'NuGet help <command>' for help on a specific command. const auto& program_files_32_bit = System::get_program_files_32_bit(); if (const auto pf = program_files_32_bit.get()) out_candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); +#else + // TODO: figure out if this should do anything on non-windows + Util::unused(out_candidate_paths); #endif } @@ -443,31 +435,21 @@ git version 2.17.1.windows.2 virtual const fs::path& get_tool_path(const VcpkgPaths& paths, const std::string& tool) const override { return path_only_cache.get_lazy(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 || tool == Tools::GIT || tool == Tools::NINJA || tool == Tools::NUGET || - tool == Tools::IFW_INSTALLER_BASE) - return get_tool_pathversion(paths, tool).path; if (tool == Tools::IFW_BINARYCREATOR) return get_tool_path(paths, Tools::IFW_INSTALLER_BASE).parent_path() / "binarycreator.exe"; if (tool == Tools::IFW_REPOGEN) return get_tool_path(paths, Tools::IFW_INSTALLER_BASE).parent_path() / "repogen.exe"; - // For other tools, we simply always auto-download them. - const ToolData tool_data = parse_tool_data_from_xml(paths, tool).value_or_exit(VCPKG_LINE_INFO); - if (paths.get_filesystem().exists(tool_data.exe_path)) - { - return tool_data.exe_path; - } - return fetch_tool(paths, tool, tool_data); + return get_tool_pathversion(paths, tool).path; }); } const PathAndVersion& get_tool_pathversion(const VcpkgPaths& paths, const std::string& tool) const { return path_version_cache.get_lazy(tool, [&]() -> PathAndVersion { + // 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::CMAKE) { if (System::get_environment_variable("VCPKG_FORCE_SYSTEM_BINARIES").has_value()) @@ -495,7 +477,18 @@ git version 2.17.1.windows.2 if (tool == Tools::NUGET) return get_path(paths, NuGetProvider()); if (tool == Tools::IFW_INSTALLER_BASE) return get_path(paths, IfwInstallerBaseProvider()); - Checks::exit_with_message(VCPKG_LINE_INFO, "Finding version for %s is not implemented yet.", tool); + // For other tools, we simply always auto-download them. + auto maybe_tool_data = parse_tool_data_from_xml(paths, tool); + if (auto p_tool_data = maybe_tool_data.get()) + { + if (paths.get_filesystem().exists(p_tool_data->exe_path)) + { + return {p_tool_data->exe_path, p_tool_data->sha512}; + } + return {fetch_tool(paths, tool, *p_tool_data), p_tool_data->sha512}; + } + + Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown or unavailable tool: %s", tool); }); } diff --git a/toolsrc/src/vcpkg/update.cpp b/toolsrc/src/vcpkg/update.cpp index 2c52d5952..6320bae5b 100644 --- a/toolsrc/src/vcpkg/update.cpp +++ b/toolsrc/src/vcpkg/update.cpp @@ -23,10 +23,10 @@ namespace vcpkg::Update for (auto&& ipv : installed_packages) { const auto& pgh = ipv.core; - auto maybe_scf = provider.get_control_file(pgh->package.spec.name()); - if (auto p_scf = maybe_scf.get()) + auto maybe_scfl = provider.get_control_file(pgh->package.spec.name()); + if (auto p_scfl = maybe_scfl.get()) { - auto&& port_version = p_scf->core_paragraph->version; + auto&& port_version = p_scfl->source_control_file->core_paragraph->version; auto&& installed_version = pgh->package.version; if (installed_version != port_version) { @@ -57,7 +57,7 @@ namespace vcpkg::Update const StatusParagraphs status_db = database_load_check(paths); - Dependencies::PathsPortFileProvider provider(paths); + Dependencies::PathsPortFileProvider provider(paths, args.overlay_ports.get()); const auto outdated_packages = SortedVector<OutdatedPackage>(find_outdated_packages(provider, status_db), &OutdatedPackage::compare_by_name); diff --git a/toolsrc/src/vcpkg/userconfig.cpp b/toolsrc/src/vcpkg/userconfig.cpp index a7c4e2765..a3c019be7 100644 --- a/toolsrc/src/vcpkg/userconfig.cpp +++ b/toolsrc/src/vcpkg/userconfig.cpp @@ -51,7 +51,7 @@ namespace vcpkg { const auto& pghs = *p_pghs; - std::unordered_map<std::string, std::string> keys; + Parse::RawParagraph keys; if (pghs.size() > 0) keys = pghs[0]; for (size_t x = 1; x < pghs.size(); ++x) diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp index 8565c28f9..5f99b85e5 100644 --- a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp +++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp @@ -33,6 +33,21 @@ namespace vcpkg option_field = std::make_unique<std::string>(*arg_begin); } + static void parse_cojoined_value(std::string new_value, + const std::string& option_name, + std::unique_ptr<std::string>& option_field) + { + if (nullptr != option_field) + { + System::printf(System::Color::error, "Error: %s specified multiple times\n", option_name); + Metrics::g_metrics.lock()->track_property("error", "error option specified multiple times"); + Help::print_usage(); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + option_field = std::make_unique<std::string>(std::move(new_value)); + } + static void parse_switch(bool new_setting, const std::string& option_name, Optional<bool>& option_field) { if (option_field && option_field != new_setting) @@ -45,6 +60,25 @@ namespace vcpkg option_field = new_setting; } + static void parse_cojoined_multivalue(std::string new_value, + const std::string& option_name, + std::unique_ptr<std::vector<std::string>>& option_field) + { + if (new_value.empty()) + { + System::print2(System::Color::error, "Error: expected value after ", option_name, '\n'); + Metrics::g_metrics.lock()->track_property("error", "error option name"); + Help::print_usage(); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + if (!option_field) + { + option_field = std::make_unique<std::vector<std::string>>(); + } + option_field->emplace_back(std::move(new_value)); + } + VcpkgCmdArguments VcpkgCmdArguments::create_from_command_line(const int argc, const CommandLineCharType* const* const argv) { @@ -101,9 +135,10 @@ namespace vcpkg if (arg[0] == '-' && arg[1] == '-') { - // make argument case insensitive + // make argument case insensitive before the first = auto& f = std::use_facet<std::ctype<char>>(std::locale()); - f.tolower(&arg[0], &arg[0] + arg.size()); + auto first_eq = std::find(std::begin(arg), std::end(arg), '='); + f.tolower(&arg[0], &arg[0] + (first_eq - std::begin(arg))); // command switch if (arg == "--vcpkg-root") { @@ -111,12 +146,30 @@ namespace vcpkg parse_value(arg_begin, arg_end, "--vcpkg-root", args.vcpkg_root_dir); continue; } + if (Strings::starts_with(arg, "--x-scripts-root=")) + { + parse_cojoined_value( + arg.substr(sizeof("--x-scripts-root=") - 1), "--x-scripts-root", args.scripts_root_dir); + continue; + } if (arg == "--triplet") { ++arg_begin; parse_value(arg_begin, arg_end, "--triplet", args.triplet); continue; } + if (Strings::starts_with(arg, "--overlay-ports=")) + { + parse_cojoined_multivalue( + arg.substr(sizeof("--overlay-ports=") - 1), "--overlay-ports", args.overlay_ports); + continue; + } + if (Strings::starts_with(arg, "--overlay-triplets=")) + { + parse_cojoined_multivalue( + arg.substr(sizeof("--overlay-triplets=") - 1), "--overlay-triplets", args.overlay_triplets); + continue; + } if (arg == "--debug") { parse_switch(true, "debug", args.debug); @@ -166,7 +219,21 @@ namespace vcpkg const auto eq_pos = arg.find('='); if (eq_pos != std::string::npos) { - args.optional_command_arguments.emplace(arg.substr(0, eq_pos), arg.substr(eq_pos + 1)); + const auto& key = arg.substr(0, eq_pos); + const auto& value = arg.substr(eq_pos + 1); + + auto it = args.optional_command_arguments.find(key); + if (args.optional_command_arguments.end() == it) + { + args.optional_command_arguments.emplace(key, std::vector<std::string>{value}); + } + else + { + if (auto* maybe_values = it->second.get()) + { + maybe_values->emplace_back(value); + } + } } else { @@ -264,7 +331,52 @@ namespace vcpkg } else { - output.settings.emplace(option.name, it->second.value_or_exit(VCPKG_LINE_INFO)); + const auto& value = it->second.value_or_exit(VCPKG_LINE_INFO); + if (value.front().empty()) + { + // Fail when not given a value, e.g.: "vcpkg install sqlite3 --additional-ports=" + System::printf( + System::Color::error, "Error: The option '%s' must be passed an argument.\n", option.name); + failed = true; + } + else + { + output.settings.emplace(option.name, value.front()); + options_copy.erase(it); + } + } + } + } + + for (auto&& option : command_structure.options.multisettings) + { + const auto it = options_copy.find(option.name); + if (it != options_copy.end()) + { + if (!it->second.has_value()) + { + // Not having a string value indicates it was passed like '--a' + System::printf( + System::Color::error, "Error: The option '%s' must be passed an argument.\n", option.name); + failed = true; + } + else + { + const auto& value = it->second.value_or_exit(VCPKG_LINE_INFO); + for (auto&& v : value) + { + if (v.empty()) + { + System::printf(System::Color::error, + "Error: The option '%s' must be passed an argument.\n", + option.name); + failed = true; + } + else + { + output.multisettings[option.name].emplace_back(v); + } + } options_copy.erase(it); } } @@ -275,7 +387,7 @@ namespace vcpkg System::printf(System::Color::error, "Unknown option(s) for command '%s':\n", this->command); for (auto&& option : options_copy) { - System::print2(" ", option.first, "\n"); + System::print2(" '", option.first, "'\n"); } System::print2("\n"); failed = true; @@ -306,9 +418,19 @@ namespace vcpkg { System::printf(" %-40s %s\n", (option.name + "=..."), option.short_help_text); } + for (auto&& option : command_structure.options.multisettings) + { + System::printf(" %-40s %s\n", (option.name + "=..."), option.short_help_text); + } System::printf(" %-40s %s\n", "--triplet <t>", "Set the default triplet for unqualified packages"); + System::printf( + " %-40s %s\n", "--overlay-ports=<path>", "Specify directories to be used when searching for ports"); + System::printf(" %-40s %s\n", "--overlay-triplets=<path>", "Specify directories containing triplets files"); System::printf(" %-40s %s\n", "--vcpkg-root <path>", "Specify the vcpkg directory to use instead of current directory or tool directory"); + System::printf(" %-40s %s\n", + "--x-scripts-root=<path>", + "(Experimental) Specify the scripts directory to use instead of default vcpkg scripts directory"); } } diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 512bcfc35..078121fcc 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -13,10 +13,14 @@ namespace vcpkg { - Expected<VcpkgPaths> VcpkgPaths::create(const fs::path& vcpkg_root_dir, const std::string& default_vs_path) + Expected<VcpkgPaths> VcpkgPaths::create(const fs::path& vcpkg_root_dir, + const Optional<fs::path>& vcpkg_scripts_root_dir, + const std::string& default_vs_path, + const std::vector<std::string>* triplets_dirs) { + auto& fs = Files::get_real_filesystem(); std::error_code ec; - const fs::path canonical_vcpkg_root_dir = fs::stdfs::canonical(vcpkg_root_dir, ec); + const fs::path canonical_vcpkg_root_dir = fs.canonical(vcpkg_root_dir, ec); if (ec) { return ec; @@ -39,7 +43,7 @@ namespace vcpkg if (auto odp = overriddenDownloadsPath.get()) { auto asPath = fs::u8path(*odp); - if (!fs::stdfs::is_directory(asPath)) + if (!fs::is_directory(fs.status(VCPKG_LINE_INFO, asPath))) { Metrics::g_metrics.lock()->track_property("error", "Invalid VCPKG_DOWNLOADS override directory."); Checks::exit_with_message( @@ -63,7 +67,25 @@ namespace vcpkg paths.ports = paths.root / "ports"; paths.installed = paths.root / "installed"; paths.triplets = paths.root / "triplets"; - paths.scripts = paths.root / "scripts"; + + if (auto scripts_dir = vcpkg_scripts_root_dir.get()) + { + if (scripts_dir->empty() || !fs::stdfs::is_directory(*scripts_dir)) + { + Metrics::g_metrics.lock()->track_property("error", "Invalid scripts override directory."); + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Invalid scripts override directory: %s; " + "create that directory or unset --x-scripts-root to use the default scripts location.", + scripts_dir->u8string()); + } + + paths.scripts = *scripts_dir; + } + else + { + paths.scripts = paths.root / "scripts"; + } paths.tools = paths.downloads / "tools"; paths.buildsystems = paths.scripts / "buildsystems"; @@ -76,14 +98,25 @@ namespace vcpkg paths.ports_cmake = paths.scripts / "ports.cmake"; + if (triplets_dirs) + { + for (auto&& triplets_dir : *triplets_dirs) + { + auto path = fs::u8path(triplets_dir); + Checks::check_exit(VCPKG_LINE_INFO, + paths.get_filesystem().exists(path), + "Error: Path does not exist '%s'", + triplets_dir); + paths.triplets_dirs.emplace_back(fs::stdfs::canonical(path)); + } + } + paths.triplets_dirs.emplace_back(fs::stdfs::canonical(paths.root / "triplets")); + return paths; } fs::path VcpkgPaths::package_dir(const PackageSpec& spec) const { return this->packages / spec.dir(); } - fs::path VcpkgPaths::port_dir(const PackageSpec& spec) const { return this->ports / spec.name(); } - fs::path VcpkgPaths::port_dir(const std::string& name) const { return this->ports / name; } - fs::path VcpkgPaths::build_info_file_path(const PackageSpec& spec) const { return this->package_dir(spec) / "BUILD_INFO"; @@ -94,26 +127,46 @@ namespace vcpkg return this->vcpkg_dir_info / (pgh.fullstem() + ".list"); } + bool VcpkgPaths::is_valid_triplet(const Triplet& t) const + { + const auto it = Util::find_if(this->get_available_triplets(), [&](auto&& available_triplet) { + return t.canonical_name() == available_triplet; + }); + return it != this->get_available_triplets().cend(); + } + const std::vector<std::string>& VcpkgPaths::get_available_triplets() const { return this->available_triplets.get_lazy([this]() -> std::vector<std::string> { std::vector<std::string> output; - for (auto&& path : this->get_filesystem().get_files_non_recursive(this->triplets)) + for (auto&& triplets_dir : triplets_dirs) { - output.push_back(path.stem().filename().string()); + for (auto&& path : this->get_filesystem().get_files_non_recursive(triplets_dir)) + { + output.push_back(path.stem().filename().string()); + } } - Util::sort(output); - + Util::sort_unique_erase(output); return output; }); } - bool VcpkgPaths::is_valid_triplet(const Triplet& t) const + const fs::path VcpkgPaths::get_triplet_file_path(const Triplet& triplet) const { - 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(); + return m_triplets_cache.get_lazy( + triplet, [&]() -> auto { + for (const auto& triplet_dir : triplets_dirs) + { + auto path = triplet_dir / (triplet.canonical_name() + ".cmake"); + if (this->get_filesystem().exists(path)) + { + return path; + } + } + + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Triplet file %s.cmake not found", triplet.canonical_name()); + }); } const fs::path& VcpkgPaths::get_tool_exe(const std::string& tool) const diff --git a/toolsrc/src/vcpkg/versiont.cpp b/toolsrc/src/vcpkg/versiont.cpp index d20e6b577..2c025fa1d 100644 --- a/toolsrc/src/vcpkg/versiont.cpp +++ b/toolsrc/src/vcpkg/versiont.cpp @@ -11,7 +11,6 @@ namespace vcpkg const std::string& VersionT::to_string() const { return value; } bool operator==(const VersionT& left, const VersionT& right) { return left.to_string() == right.to_string(); } 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() noexcept : left(), right() {} VersionDiff::VersionDiff(const VersionT& left, const VersionT& right) : left(left), right(right) {} |
