diff --git a/toolsrc/include/vcpkg/base/optional.h b/toolsrc/include/vcpkg/base/optional.h index fc3733ead4..50586ecc8d 100644 --- a/toolsrc/include/vcpkg/base/optional.h +++ b/toolsrc/include/vcpkg/base/optional.h @@ -283,6 +283,21 @@ namespace vcpkg } } + friend bool operator==(const Optional& lhs, const Optional& rhs) + { + if (lhs.m_base.has_value()) + { + if (rhs.m_base.has_value()) + { + return lhs.m_base.value() == rhs.m_base.value(); + } + + return false; + } + + return !rhs.m_base.has_value(); + } + private: details::OptionalStorage m_base; }; @@ -317,4 +332,27 @@ namespace vcpkg if (auto p = o.get()) return t != *p; return true; } + + template + auto common_projection(const Container& input, Projection proj) + -> Optional> + { + const auto last = input.end(); + auto first = input.begin(); + if (first == last) + { + return nullopt; + } + + const auto& prototype = proj(*first); + while (++first != last) + { + if (prototype != proj(*first)) + { + return nullopt; + } + } + + return prototype; + } } diff --git a/toolsrc/include/vcpkg/base/system.h b/toolsrc/include/vcpkg/base/system.h index 907a692a27..4172f0c500 100644 --- a/toolsrc/include/vcpkg/base/system.h +++ b/toolsrc/include/vcpkg/base/system.h @@ -21,6 +21,8 @@ namespace vcpkg::System Optional to_cpu_architecture(StringView arch); + ZStringView to_zstring_view(CPUArchitecture arch) noexcept; + CPUArchitecture get_host_processor(); std::vector get_supported_host_architectures(); @@ -30,4 +32,6 @@ namespace vcpkg::System const Optional& get_program_files_platform_bitness(); int get_num_logical_cores(); + + Optional guess_visual_studio_prompt_target_architecture(); } diff --git a/toolsrc/include/vcpkg/triplet.h b/toolsrc/include/vcpkg/triplet.h index 8d8c3e8798..d836dd2302 100644 --- a/toolsrc/include/vcpkg/triplet.h +++ b/toolsrc/include/vcpkg/triplet.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace vcpkg { @@ -15,17 +17,18 @@ namespace vcpkg static const Triplet X86_WINDOWS; static const Triplet X64_WINDOWS; + static const Triplet ARM_WINDOWS; + static const Triplet ARM64_WINDOWS; static const Triplet X86_UWP; static const Triplet X64_UWP; static const Triplet ARM_UWP; static const Triplet ARM64_UWP; - static const Triplet ARM_WINDOWS; - static const Triplet ARM64_WINDOWS; const std::string& canonical_name() const; const std::string& to_string() const; void to_string(std::string& out) const; size_t hash_code() const; + Optional guess_architecture() const noexcept; bool operator==(Triplet other) const { return this->m_instance == other.m_instance; } bool operator<(Triplet other) const { return canonical_name() < other.canonical_name(); } diff --git a/toolsrc/src/vcpkg-test/optional.cpp b/toolsrc/src/vcpkg-test/optional.cpp new file mode 100644 index 0000000000..ce728208f4 --- /dev/null +++ b/toolsrc/src/vcpkg-test/optional.cpp @@ -0,0 +1,39 @@ +#include +#include +#include + +namespace +{ + struct identity_projection + { + template + const T& operator()(const T& val) noexcept + { + return val; + } + }; +} + +TEST_CASE ("equal", "[optional]") +{ + using vcpkg::Optional; + + CHECK(Optional{} == Optional{}); + CHECK(!(Optional{} == Optional{42})); + CHECK(!(Optional{42} == Optional{})); + CHECK(!(Optional{1729} == Optional{42})); + CHECK(Optional{42} == Optional{42}); +} + +TEST_CASE ("common_projection", "[optional]") +{ + using vcpkg::common_projection; + std::vector input; + CHECK(!common_projection(input, identity_projection{}).has_value()); + input.push_back(42); + CHECK(common_projection(input, identity_projection{}).value_or_exit(VCPKG_LINE_INFO) == 42); + input.push_back(42); + CHECK(common_projection(input, identity_projection{}).value_or_exit(VCPKG_LINE_INFO) == 42); + input.push_back(1729); + CHECK(!common_projection(input, identity_projection{}).has_value()); +} diff --git a/toolsrc/src/vcpkg-test/system.cpp b/toolsrc/src/vcpkg-test/system.cpp new file mode 100644 index 0000000000..41e27fe6d9 --- /dev/null +++ b/toolsrc/src/vcpkg-test/system.cpp @@ -0,0 +1,159 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +using vcpkg::Optional; +using vcpkg::StringView; +using vcpkg::ZStringView; +using vcpkg::Checks::check_exit; +using vcpkg::System::get_environment_variable; +using vcpkg::System::to_cpu_architecture; +using vcpkg::System::guess_visual_studio_prompt_target_architecture; +using vcpkg::nullopt; +using vcpkg::System::CPUArchitecture; + +namespace +{ + void set_environment_variable(StringView varname, Optional value) + { +#if defined(_WIN32) + const auto w_varname = vcpkg::Strings::to_utf16(varname); + const auto w_varcstr = w_varname.c_str(); + BOOL exit_code; + if (value) + { + const auto w_value = vcpkg::Strings::to_utf16(value.value_or_exit(VCPKG_LINE_INFO)); + exit_code = SetEnvironmentVariableW(w_varcstr, w_value.c_str()); + } + else + { + exit_code = SetEnvironmentVariableW(w_varcstr, nullptr); + } + + check_exit(VCPKG_LINE_INFO, exit_code != 0); +#else // ^^^ defined(_WIN32) / !defined(_WIN32) vvv + std::string tmp; + tmp.reserve(varname.size() + value.size() + 1); + tmp.append(varname.data(), varname.size()); + tmp.push_back('='); + if (value) + { + const auto& unpacked = value.value_or_exit(VCPKG_LINE_INFO); + tmp.append(unpacked); + } + + const int exit_code = putenv(tmp.c_str()); + check_exit(VCPKG_LINE_INFO, exit_code == 0); +#endif // defined(_WIN32) + } + + struct environment_variable_resetter + { + explicit environment_variable_resetter(ZStringView varname_) + : varname(varname_), old_value(get_environment_variable(varname)) + { + } + + ~environment_variable_resetter() { set_environment_variable(varname, old_value); } + + environment_variable_resetter(const environment_variable_resetter&) = delete; + environment_variable_resetter& operator=(const environment_variable_resetter&) = delete; + + private: + ZStringView varname; + Optional old_value; + }; +} + +TEST_CASE ("[to_cpu_architecture]", "system") +{ + struct test_case + { + Optional expected; + StringView input; + }; + + const test_case test_cases[] = { + {CPUArchitecture::X86, "x86"}, + {CPUArchitecture::X86, "X86"}, + {CPUArchitecture::X64, "x64"}, + {CPUArchitecture::X64, "X64"}, + {CPUArchitecture::X64, "AmD64"}, + {CPUArchitecture::ARM, "ARM"}, + {CPUArchitecture::ARM64, "ARM64"}, + {nullopt, "ARM6"}, + {nullopt, "AR"}, + {nullopt, "Intel"}, + }; + + for (auto&& instance : test_cases) + { + CHECK(to_cpu_architecture(instance.input) == instance.expected); + } +} + +TEST_CASE ("from_cpu_architecture", "[system]") +{ + struct test_case + { + CPUArchitecture input; + ZStringView expected; + }; + + const test_case test_cases[] = { + {CPUArchitecture::X86, "x86"}, + {CPUArchitecture::X64, "x64"}, + {CPUArchitecture::ARM, "arm"}, + {CPUArchitecture::ARM64, "arm64"}, + }; + + for (auto&& instance : test_cases) + { + CHECK(to_zstring_view(instance.input) == instance.expected); + } +} + +TEST_CASE ("guess_visual_studio_prompt", "[system]") +{ + environment_variable_resetter reset_VSCMD_ARG_TGT_ARCH{"VSCMD_ARG_TGT_ARCH"}; + environment_variable_resetter reset_VCINSTALLDIR{"VCINSTALLDIR"}; + environment_variable_resetter reset_Platform{"Platform"}; + + set_environment_variable("Platform", "x86"); // ignored if VCINSTALLDIR unset + set_environment_variable("VCINSTALLDIR", nullopt); + set_environment_variable("VSCMD_ARG_TGT_ARCH", nullopt); + CHECK(!guess_visual_studio_prompt_target_architecture().has_value()); + set_environment_variable("VSCMD_ARG_TGT_ARCH", "x86"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::X86); + set_environment_variable("VSCMD_ARG_TGT_ARCH", "x64"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::X64); + set_environment_variable("VSCMD_ARG_TGT_ARCH", "arm"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::ARM); + set_environment_variable("VSCMD_ARG_TGT_ARCH", "arm64"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::ARM64); + + // check that apparent "nested" prompts defer to "vsdevcmd" + set_environment_variable("VCINSTALLDIR", "anything"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::ARM64); + set_environment_variable("VSCMD_ARG_TGT_ARCH", nullopt); + set_environment_variable("Platform", nullopt); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::X86); + set_environment_variable("Platform", "x86"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::X86); + set_environment_variable("Platform", "x64"); + CHECK(guess_visual_studio_prompt_target_architecture() + .value_or_exit(VCPKG_LINE_INFO) == CPUArchitecture::X64); +} diff --git a/toolsrc/src/vcpkg/base/checks.cpp b/toolsrc/src/vcpkg/base/checks.cpp index 6093b5188e..42e0835c6b 100644 --- a/toolsrc/src/vcpkg/base/checks.cpp +++ b/toolsrc/src/vcpkg/base/checks.cpp @@ -15,7 +15,7 @@ namespace vcpkg g_shutdown_handler = func; } - void Checks::final_cleanup_and_exit(const int exit_code) + [[noreturn]] void Checks::final_cleanup_and_exit(const int exit_code) { static std::atomic have_entered{false}; if (have_entered.exchange(true)) @@ -37,7 +37,7 @@ namespace vcpkg std::exit(exit_code); } - void Checks::unreachable(const LineInfo& line_info) + [[noreturn]] void Checks::unreachable(const LineInfo& line_info) { System::print2(System::Color::error, "Error: Unreachable code was reached\n"); System::print2(System::Color::error, line_info, '\n'); // Always print line_info here @@ -48,13 +48,13 @@ namespace vcpkg #endif } - void Checks::exit_with_code(const LineInfo& line_info, const int exit_code) + [[noreturn]] void Checks::exit_with_code(const LineInfo& line_info, const int exit_code) { Debug::print(System::Color::error, line_info, '\n'); final_cleanup_and_exit(exit_code); } - void Checks::exit_with_message(const LineInfo& line_info, StringView error_message) + [[noreturn]] void Checks::exit_with_message(const LineInfo& line_info, StringView error_message) { System::print2(System::Color::error, error_message, '\n'); exit_fail(line_info); diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp index 96cdeb84af..f49632cac0 100644 --- a/toolsrc/src/vcpkg/base/system.cpp +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -22,6 +22,18 @@ namespace vcpkg return nullopt; } + ZStringView System::to_zstring_view(CPUArchitecture arch) noexcept + { + switch (arch) + { + case CPUArchitecture::X86: return "x86"; + case CPUArchitecture::X64: return "x64"; + case CPUArchitecture::ARM: return "arm"; + case CPUArchitecture::ARM64: return "arm64"; + default: Checks::exit_with_message(VCPKG_LINE_INFO, "unexpected vcpkg::System::CPUArchitecture"); + } + } + CPUArchitecture System::get_host_processor() { #if defined(_WIN32) @@ -30,7 +42,7 @@ namespace vcpkg const auto procarch = get_environment_variable("PROCESSOR_ARCHITECTURE").value_or_exit(VCPKG_LINE_INFO); return to_cpu_architecture(procarch).value_or_exit(VCPKG_LINE_INFO); -#else +#else // ^^^ defined(_WIN32) / !defined(_WIN32) vvv #if defined(__x86_64__) || defined(_M_X64) return CPUArchitecture::X64; #elif defined(__x86__) || defined(_M_X86) @@ -39,10 +51,10 @@ namespace vcpkg return CPUArchitecture::ARM; #elif defined(__aarch64__) || defined(_M_ARM64) return CPUArchitecture::ARM64; -#else +#else // choose architecture #error "Unknown host architecture" -#endif -#endif +#endif // choose architecture +#endif // defined(_WIN32) } std::vector System::get_supported_host_architectures() @@ -67,7 +79,7 @@ namespace vcpkg { supported_architectures.push_back(CPUArchitecture::X86); } -#endif +#endif // defined(_WIN32) return supported_architectures; } @@ -86,11 +98,11 @@ namespace vcpkg Checks::check_exit(VCPKG_LINE_INFO, sz2 + 1 == sz); ret.pop_back(); return Strings::to_utf8(ret.c_str()); -#else +#else // ^^^ defined(_WIN32) / !defined(_WIN32) vvv auto v = getenv(varname.c_str()); if (!v) return nullopt; return std::string(v); -#endif +#endif // defined(_WIN32) } #if defined(_WIN32) @@ -125,13 +137,13 @@ namespace vcpkg ret.pop_back(); // remove extra trailing null byte return Strings::to_utf8(ret); } -#else +#else // ^^^ defined(_WIN32) / !defined(_WIN32) vvv Optional System::get_registry_string(void*, StringView, StringView) { return nullopt; } -#endif +#endif // defined(_WIN32) static const Optional& get_program_files() { - static const auto PATH = []() -> Optional { + static const auto PROGRAMFILES = []() -> Optional { auto value = System::get_environment_variable("PROGRAMFILES"); if (auto v = value.get()) { @@ -141,12 +153,12 @@ namespace vcpkg return nullopt; }(); - return PATH; + return PROGRAMFILES; } const Optional& System::get_program_files_32_bit() { - static const auto PATH = []() -> Optional { + static const auto PROGRAMFILES_x86 = []() -> Optional { auto value = System::get_environment_variable("ProgramFiles(x86)"); if (auto v = value.get()) { @@ -154,12 +166,12 @@ namespace vcpkg } return get_program_files(); }(); - return PATH; + return PROGRAMFILES_x86; } const Optional& System::get_program_files_platform_bitness() { - static const auto PATH = []() -> Optional { + static const auto ProgramW6432 = []() -> Optional { auto value = System::get_environment_variable("ProgramW6432"); if (auto v = value.get()) { @@ -167,10 +179,36 @@ namespace vcpkg } return get_program_files(); }(); - return PATH; + return ProgramW6432; } int System::get_num_logical_cores() { return std::thread::hardware_concurrency(); } + + Optional System::guess_visual_studio_prompt_target_architecture() + { + // Check for the "vsdevcmd" infrastructure used by Visual Studio 2017 and later + const auto VSCMD_ARG_TGT_ARCH = System::get_environment_variable("VSCMD_ARG_TGT_ARCH"); + if (VSCMD_ARG_TGT_ARCH) + { + return to_cpu_architecture(VSCMD_ARG_TGT_ARCH.value_or_exit(VCPKG_LINE_INFO)); + } + + // Check for the "vcvarsall" infrastructure used by Visual Studio 2015 + if (System::get_environment_variable("VCINSTALLDIR")) + { + const auto Platform = System::get_environment_variable("Platform"); + if (Platform) + { + return to_cpu_architecture(Platform.value_or_exit(VCPKG_LINE_INFO)); + } + else + { + return CPUArchitecture::X86; + } + } + + return nullopt; + } } namespace vcpkg::Debug diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index 2874f39f49..3d6e4c2d93 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -699,15 +699,49 @@ namespace vcpkg::Install std::string specs_string; for (auto&& remove_action : action_plan.remove_actions) { - if (!specs_string.empty()) specs_string += ","; + if (!specs_string.empty()) specs_string.push_back(','); specs_string += "R$" + Hash::get_string_hash(remove_action.spec.to_string(), Hash::Algorithm::Sha256); } + for (auto&& install_action : action_plan.install_actions) { - if (!specs_string.empty()) specs_string += ","; + if (!specs_string.empty()) specs_string.push_back(','); specs_string += Hash::get_string_hash(install_action.spec.to_string(), Hash::Algorithm::Sha256); } +#if defined(_WIN32) + const auto maybe_common_triplet = common_projection( + action_plan.install_actions, [](const InstallPlanAction& to_install) { return to_install.spec.triplet(); }); + if (maybe_common_triplet) + { + const auto& common_triplet = maybe_common_triplet.value_or_exit(VCPKG_LINE_INFO); + const auto maybe_common_arch = common_triplet.guess_architecture(); + if (maybe_common_arch) + { + const auto maybe_vs_prompt = System::guess_visual_studio_prompt_target_architecture(); + if (maybe_vs_prompt) + { + const auto common_arch = maybe_common_arch.value_or_exit(VCPKG_LINE_INFO); + const auto vs_prompt = maybe_vs_prompt.value_or_exit(VCPKG_LINE_INFO); + if (common_arch != vs_prompt) + { + const auto vs_prompt_view = to_zstring_view(vs_prompt); + System::print2(vcpkg::System::Color::warning, + "warning: vcpkg appears to be in a Visual Studio prompt targeting ", + vs_prompt_view, + " but is installing packages for ", + common_triplet.to_string(), + ". Consider using --triplet ", + vs_prompt_view, + "-windows or --triplet ", + vs_prompt_view, + "-uwp.\n"); + } + } + } + } +#endif // defined(_WIN32) + Metrics::g_metrics.lock()->track_property("installplan_1", specs_string); Dependencies::print_plan(action_plan, is_recursive, paths.ports); diff --git a/toolsrc/src/vcpkg/triplet.cpp b/toolsrc/src/vcpkg/triplet.cpp index 4bc684d0b6..8026baa8a8 100644 --- a/toolsrc/src/vcpkg/triplet.cpp +++ b/toolsrc/src/vcpkg/triplet.cpp @@ -32,12 +32,12 @@ namespace vcpkg const Triplet Triplet::X86_WINDOWS = from_canonical_name("x86-windows"); const Triplet Triplet::X64_WINDOWS = from_canonical_name("x64-windows"); + const Triplet Triplet::ARM_WINDOWS = from_canonical_name("arm-windows"); + const Triplet Triplet::ARM64_WINDOWS = from_canonical_name("arm64-windows"); const Triplet Triplet::X86_UWP = from_canonical_name("x86-uwp"); const Triplet Triplet::X64_UWP = from_canonical_name("x64-uwp"); const Triplet Triplet::ARM_UWP = from_canonical_name("arm-uwp"); const Triplet Triplet::ARM64_UWP = from_canonical_name("arm64-uwp"); - const Triplet Triplet::ARM_WINDOWS = from_canonical_name("arm-windows"); - const Triplet Triplet::ARM64_WINDOWS = from_canonical_name("arm64-windows"); Triplet Triplet::from_canonical_name(std::string&& triplet_as_string) { @@ -51,4 +51,27 @@ namespace vcpkg const std::string& Triplet::to_string() const { return this->canonical_name(); } void Triplet::to_string(std::string& out) const { out.append(this->canonical_name()); } size_t Triplet::hash_code() const { return m_instance->hash; } + + Optional Triplet::guess_architecture() const noexcept + { + using System::CPUArchitecture; + if (*this == X86_WINDOWS || *this == X86_UWP) + { + return CPUArchitecture::X86; + } + else if (*this == X64_WINDOWS || *this == X64_UWP) + { + return CPUArchitecture::X64; + } + else if (*this == ARM_WINDOWS || *this == ARM_UWP) + { + return CPUArchitecture::ARM; + } + else if (*this == ARM64_WINDOWS || *this == ARM64_UWP) + { + return CPUArchitecture::ARM64; + } + + return nullopt; + } }