diff options
| author | Phil Christensen <philc@microsoft.com> | 2018-12-06 15:06:28 -0800 |
|---|---|---|
| committer | Phil Christensen <philc@microsoft.com> | 2018-12-06 15:06:28 -0800 |
| commit | 7347305e8459fcc78553a9f88196e0d93eb0a8fe (patch) | |
| tree | ac9eee9ff267c6a71a83249bed7a94f02b00d9a5 /toolsrc/src | |
| parent | ed9357a5aafea7192932b5874264bd103fc61255 (diff) | |
| parent | 63c1b2628e958f8e02356411f032941c0c2f3bbb (diff) | |
| download | vcpkg-7347305e8459fcc78553a9f88196e0d93eb0a8fe.tar.gz vcpkg-7347305e8459fcc78553a9f88196e0d93eb0a8fe.zip | |
Merge branch 'master' of https://github.com/microsoft/vcpkg into dev/philc/3425
Diffstat (limited to 'toolsrc/src')
38 files changed, 2383 insertions, 1305 deletions
diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index 06c99e9a8..3589881a7 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -33,6 +33,12 @@ using namespace vcpkg; +// 24 hours/day * 30 days/month * 6 months +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) { System::println(System::Color::error, "invalid command: %s", cmd); @@ -70,7 +76,7 @@ static void inner(const VcpkgCmdArguments& args) fs::path vcpkg_root_dir; if (args.vcpkg_root_dir != nullptr) { - vcpkg_root_dir = fs::stdfs::absolute(Strings::to_utf16(*args.vcpkg_root_dir)); + vcpkg_root_dir = fs::stdfs::absolute(fs::u8path(*args.vcpkg_root_dir)); } else { @@ -94,6 +100,8 @@ static void inner(const VcpkgCmdArguments& args) Checks::check_exit(VCPKG_LINE_INFO, !vcpkg_root_dir.empty(), "Error: Could not detect vcpkg-root."); + Debug::println("Using vcpkg-root: %s", vcpkg_root_dir.u8string()); + auto default_vs_path = System::get_environment_variable("VCPKG_DEFAULT_VS_PATH").value_or(""); const Expected<VcpkgPaths> expected_paths = VcpkgPaths::create(vcpkg_root_dir, default_vs_path); @@ -102,7 +110,7 @@ static void inner(const VcpkgCmdArguments& args) "Error: Invalid vcpkg root directory %s: %s", vcpkg_root_dir.string(), expected_paths.error().message()); - const VcpkgPaths paths = expected_paths.value_or_exit(VCPKG_LINE_INFO); + const VcpkgPaths& paths = expected_paths.value_or_exit(VCPKG_LINE_INFO); #if defined(_WIN32) const int exit_code = _wchdir(paths.root.c_str()); @@ -111,19 +119,19 @@ static void inner(const VcpkgCmdArguments& args) #endif Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Changing the working dir failed"); - if (args.command != "autocomplete") + if (args.command == "install" || args.command == "remove" || args.command == "export" || args.command == "update") { Commands::Version::warn_if_vcpkg_version_mismatch(paths); std::string surveydate = *GlobalState::g_surveydate.lock(); auto maybe_surveydate = Chrono::CTime::parse(surveydate); if (auto p_surveydate = maybe_surveydate.get()) { - auto delta = std::chrono::system_clock::now() - p_surveydate->to_time_point(); - // 24 hours/day * 30 days/month - if (std::chrono::duration_cast<std::chrono::hours>(delta).count() > 24 * 30) + const auto now = Chrono::CTime::get_current_date_time().value_or_exit(VCPKG_LINE_INFO); + const auto delta = now.to_time_point() - p_surveydate->to_time_point(); + if (std::chrono::duration_cast<std::chrono::hours>(delta).count() > SURVEY_INTERVAL_IN_HOURS) { std::default_random_engine generator( - static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count())); + static_cast<unsigned int>(now.to_time_point().time_since_epoch().count())); std::uniform_int_distribution<int> distribution(1, 4); if (distribution(generator) == 1) @@ -212,7 +220,9 @@ static void load_config() if (config.last_completed_survey.empty()) { - config.last_completed_survey = config.user_time; + const auto now = Chrono::CTime::parse(config.user_time).value_or_exit(VCPKG_LINE_INFO); + const Chrono::CTime offset = now.add_hours(-SURVEY_INITIAL_OFFSET_IN_HOURS); + config.last_completed_survey = offset.to_string(); } GlobalState::g_surveydate.lock()->assign(config.last_completed_survey); @@ -259,6 +269,7 @@ int main(const int argc, const char* const* const argv) #if defined(_WIN32) GlobalState::g_init_console_cp = GetConsoleCP(); GlobalState::g_init_console_output_cp = GetConsoleOutputCP(); + GlobalState::g_init_console_initialized = true; SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); @@ -273,6 +284,9 @@ int main(const int argc, const char* const* const argv) locked_metrics->track_property("cmdline", trimmed_command_line); #endif } + + Checks::register_console_ctrl_handler(); + load_config(); const auto vcpkg_feature_flags_env = System::get_environment_variable("VCPKG_FEATURE_FLAGS"); @@ -291,8 +305,6 @@ int main(const int argc, const char* const* const argv) if (const auto p = args.sendmetrics.get()) Metrics::g_metrics.lock()->set_send_metrics(*p); if (const auto p = args.debug.get()) GlobalState::debugging = *p; - Checks::register_console_ctrl_handler(); - if (GlobalState::debugging) { inner(args); diff --git a/toolsrc/src/vcpkg/archives.cpp b/toolsrc/src/vcpkg/archives.cpp new file mode 100644 index 000000000..8943893d6 --- /dev/null +++ b/toolsrc/src/vcpkg/archives.cpp @@ -0,0 +1,106 @@ +#include "pch.h" + +#include <vcpkg/archives.h> +#include <vcpkg/commands.h> + +namespace vcpkg::Archives +{ + void extract_archive(const VcpkgPaths& paths, const fs::path& archive, const fs::path& to_path) + { + Files::Filesystem& fs = paths.get_filesystem(); + const fs::path to_path_partial = to_path.u8string() + ".partial"; + + std::error_code ec; + fs.remove_all(to_path, ec); + fs.remove_all(to_path_partial, ec); + fs.create_directories(to_path_partial, ec); + const auto ext = archive.extension(); +#if defined(_WIN32) + if (ext == ".nupkg") + { + static bool recursion_limiter_sevenzip_old = false; + Checks::check_exit(VCPKG_LINE_INFO, !recursion_limiter_sevenzip_old); + recursion_limiter_sevenzip_old = true; + const auto nuget_exe = paths.get_tool_exe(Tools::NUGET); + + const std::string stem = archive.stem().u8string(); + // assuming format of [name].[version in the form d.d.d] + // This assumption may not always hold + std::smatch match; + const bool has_match = std::regex_match(stem, match, std::regex{R"###(^(.+)\.(\d+\.\d+\.\d+)$)###"}); + Checks::check_exit(VCPKG_LINE_INFO, + has_match, + "Could not deduce nuget id and version from filename: %s", + archive.u8string()); + + const std::string nugetid = match[1]; + const std::string version = match[2]; + + const auto code_and_output = System::cmd_execute_and_capture_output(Strings::format( + R"("%s" install %s -Version %s -OutputDirectory "%s" -Source "%s" -nocache -DirectDownload -NonInteractive -ForceEnglishOutput -PackageSaveMode nuspec)", + nuget_exe.u8string(), + nugetid, + version, + to_path_partial.u8string(), + paths.downloads.u8string())); + + Checks::check_exit(VCPKG_LINE_INFO, + code_and_output.exit_code == 0, + "Failed to extract '%s' with message:\n%s", + archive.u8string(), + code_and_output.output); + recursion_limiter_sevenzip_old = false; + } + else + { + static bool recursion_limiter_sevenzip = false; + Checks::check_exit(VCPKG_LINE_INFO, !recursion_limiter_sevenzip); + recursion_limiter_sevenzip = true; + const auto seven_zip = paths.get_tool_exe(Tools::SEVEN_ZIP); + const auto code_and_output = System::cmd_execute_and_capture_output(Strings::format( + R"("%s" x "%s" -o"%s" -y)", seven_zip.u8string(), archive.u8string(), to_path_partial.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, + code_and_output.exit_code == 0, + "7zip failed while extracting '%s' with message:\n%s", + archive.u8string(), + code_and_output.output); + recursion_limiter_sevenzip = false; + } +#else + if (ext == ".gz" && ext.extension() != ".tar") + { + const auto code = System::cmd_execute( + Strings::format(R"(cd '%s' && tar xzf '%s')", to_path_partial.u8string(), archive.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "tar failed while extracting %s", archive.u8string()); + } + else if (ext == ".zip") + { + const auto code = System::cmd_execute( + Strings::format(R"(cd '%s' && unzip -qqo '%s')", to_path_partial.u8string(), archive.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "unzip failed while extracting %s", archive.u8string()); + } + else + { + Checks::exit_with_message(VCPKG_LINE_INFO, "Unexpected archive extension: %s", ext.u8string()); + } +#endif + + fs.rename(to_path_partial, to_path, ec); + + for (int i = 0; i < 5 && ec; i++) + { + i++; + using namespace std::chrono_literals; + std::this_thread::sleep_for(i * 100ms); + fs.rename(to_path_partial, to_path, ec); + } + + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Failed to do post-extract rename-in-place.\n" + "fs.rename(%s, %s, %s)", + to_path_partial.u8string(), + to_path.u8string(), + ec.message()); + } +} diff --git a/toolsrc/src/vcpkg/base/checks.cpp b/toolsrc/src/vcpkg/base/checks.cpp index d96cb98ff..cc439adfe 100644 --- a/toolsrc/src/vcpkg/base/checks.cpp +++ b/toolsrc/src/vcpkg/base/checks.cpp @@ -14,18 +14,29 @@ namespace vcpkg::Checks if (have_entered) std::terminate(); have_entered = true; - const auto elapsed_us = GlobalState::timer.lock()->microseconds(); + const auto elapsed_us_inner = GlobalState::timer.lock()->microseconds(); + + bool debugging = GlobalState::debugging; auto metrics = Metrics::g_metrics.lock(); - metrics->track_metric("elapsed_us", elapsed_us); + metrics->track_metric("elapsed_us", elapsed_us_inner); GlobalState::debugging = false; metrics->flush(); #if defined(_WIN32) - SetConsoleCP(GlobalState::g_init_console_cp); - SetConsoleOutputCP(GlobalState::g_init_console_output_cp); + if (GlobalState::g_init_console_initialized) + { + SetConsoleCP(GlobalState::g_init_console_cp); + SetConsoleOutputCP(GlobalState::g_init_console_output_cp); + } #endif + auto elapsed_us = GlobalState::timer.lock()->microseconds(); + if (debugging) + System::println("[DEBUG] Exiting after %d us (%d us)", + static_cast<int>(elapsed_us), + static_cast<int>(elapsed_us_inner)); + fflush(nullptr); #if defined(_WIN32) @@ -38,12 +49,11 @@ namespace vcpkg::Checks #if defined(_WIN32) static BOOL ctrl_handler(DWORD fdw_ctrl_type) { + switch (fdw_ctrl_type) { - auto locked_metrics = Metrics::g_metrics.lock(); - locked_metrics->track_property("CtrlHandler", std::to_string(fdw_ctrl_type)); - locked_metrics->track_property("error", "CtrlHandler was fired."); + case CTRL_C_EVENT: GlobalState::g_ctrl_c_state.transition_handle_ctrl_c(); return TRUE; + default: return FALSE; } - cleanup_and_exit(EXIT_FAILURE); } void register_console_ctrl_handler() diff --git a/toolsrc/src/vcpkg/base/chrono.cpp b/toolsrc/src/vcpkg/base/chrono.cpp index 2a76f5df0..405e76605 100644 --- a/toolsrc/src/vcpkg/base/chrono.cpp +++ b/toolsrc/src/vcpkg/base/chrono.cpp @@ -5,6 +5,61 @@ namespace vcpkg::Chrono { + static std::time_t get_current_time_as_time_since_epoch() + { + using std::chrono::system_clock; + return system_clock::to_time_t(system_clock::now()); + } + + static std::time_t utc_mktime(tm* time_ptr) + { +#if defined(_WIN32) + return _mkgmtime(time_ptr); +#else + return timegm(time_ptr); +#endif + } + + static tm to_local_time(const std::time_t& t) + { + tm parts {}; +#if defined(_WIN32) + localtime_s(&parts, &t); +#else + parts = *localtime(&t); +#endif + return parts; + } + + static Optional<tm> to_utc_time(const std::time_t& t) + { + tm parts {}; +#if defined(_WIN32) + const errno_t err = gmtime_s(&parts, &t); + if (err) + { + return nullopt; + } +#else + auto null_if_failed = gmtime_r(&t, &parts); + if (null_if_failed == nullptr) + { + return nullopt; + } +#endif + return parts; + } + + static tm date_plus_hours(tm* date, const int hours) + { + using namespace std::chrono_literals; + static constexpr std::chrono::seconds SECONDS_IN_ONE_HOUR = + std::chrono::duration_cast<std::chrono::seconds>(1h); + + const std::time_t date_in_seconds = utc_mktime(date) + (hours * SECONDS_IN_ONE_HOUR.count()); + return to_utc_time(date_in_seconds).value_or_exit(VCPKG_LINE_INFO); + } + static std::string format_time_userfriendly(const std::chrono::nanoseconds& nanos) { using std::chrono::duration_cast; @@ -63,30 +118,14 @@ namespace vcpkg::Chrono Optional<CTime> CTime::get_current_date_time() { - CTime ret; - -#if defined(_WIN32) - struct _timeb timebuffer; - - _ftime_s(&timebuffer); - - const errno_t err = gmtime_s(&ret.m_tm, &timebuffer.time); - - if (err) + const std::time_t ct = get_current_time_as_time_since_epoch(); + const Optional<tm> opt = to_utc_time(ct); + if (auto p_tm = opt.get()) { - return nullopt; + return CTime {*p_tm}; } -#else - time_t now = {0}; - time(&now); - auto null_if_failed = gmtime_r(&now, &ret.m_tm); - if (null_if_failed == nullptr) - { - return nullopt; - } -#endif - return ret; + return nullopt; } Optional<CTime> CTime::parse(CStringView str) @@ -111,19 +150,28 @@ namespace vcpkg::Chrono ret.m_tm.tm_year -= 1900; if (ret.m_tm.tm_mon < 1) return nullopt; ret.m_tm.tm_mon -= 1; - mktime(&ret.m_tm); + utc_mktime(&ret.m_tm); + return ret; } + CTime CTime::add_hours(const int hours) const { return CTime {date_plus_hours(&this->m_tm, hours)}; } + std::string CTime::to_string() const { - std::array<char, 80> date{}; + std::array<char, 80> date {}; strftime(&date[0], date.size(), "%Y-%m-%dT%H:%M:%S.0Z", &m_tm); return &date[0]; } std::chrono::system_clock::time_point CTime::to_time_point() const { - const time_t t = mktime(&m_tm); + const time_t t = utc_mktime(&m_tm); return std::chrono::system_clock::from_time_t(t); } + + tm get_current_date_time_local() + { + const std::time_t now_time = get_current_time_as_time_since_epoch(); + return Chrono::to_local_time(now_time); + } } diff --git a/toolsrc/src/vcpkg/base/downloads.cpp b/toolsrc/src/vcpkg/base/downloads.cpp new file mode 100644 index 000000000..571562244 --- /dev/null +++ b/toolsrc/src/vcpkg/base/downloads.cpp @@ -0,0 +1,183 @@ +#include "pch.h" + +#include <vcpkg/base/downloads.h> +#include <vcpkg/base/hash.h> +#include <vcpkg/base/util.h> + +#if defined(_WIN32) +#include <VersionHelpers.h> +#else +#include <vcpkg/base/system.h> +#endif + +namespace vcpkg::Downloads +{ +#if defined(_WIN32) + static void winhttp_download_file(Files::Filesystem& fs, + CStringView target_file_path, + CStringView hostname, + CStringView url_path) + { + // Make sure the directories are present, otherwise fopen_s fails + const auto dir = fs::path(target_file_path.c_str()).parent_path(); + std::error_code ec; + fs.create_directories(dir, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not create directories %s", dir.u8string()); + + FILE* f = nullptr; + const errno_t err = fopen_s(&f, target_file_path.c_str(), "wb"); + Checks::check_exit(VCPKG_LINE_INFO, + !err, + "Could not download https://%s%s. Failed to open file %s. Error code was %s", + hostname, + url_path, + target_file_path, + std::to_string(err)); + + auto hSession = WinHttpOpen(L"vcpkg/1.0", + IsWindows8Point1OrGreater() ? WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY + : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + Checks::check_exit(VCPKG_LINE_INFO, hSession, "WinHttpOpen() failed: %d", GetLastError()); + + // Win7 IE Proxy fallback + if (IsWindows7OrGreater() && !IsWindows8Point1OrGreater()) { + // First check if any proxy has been found automatically + WINHTTP_PROXY_INFO proxyInfo;
+ DWORD proxyInfoSize = sizeof(WINHTTP_PROXY_INFO);
+ auto noProxyFound =
+ !WinHttpQueryOption(hSession, WINHTTP_OPTION_PROXY, &proxyInfo, &proxyInfoSize)
+ || proxyInfo.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY;
+
+ // If no proxy was found automatically, use IE's proxy settings, if any
+ if (noProxyFound) { + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG ieProxy; + if (WinHttpGetIEProxyConfigForCurrentUser(&ieProxy) && ieProxy.lpszProxy != nullptr) { + WINHTTP_PROXY_INFO proxy; + proxy.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy.lpszProxy = ieProxy.lpszProxy; + proxy.lpszProxyBypass = ieProxy.lpszProxyBypass; + WinHttpSetOption(hSession, WINHTTP_OPTION_PROXY, &proxy, sizeof(proxy)); + } + } + } + + // Use Windows 10 defaults on Windows 7 + DWORD secure_protocols(WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2); + WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols)); + + // Specify an HTTP server. + auto hConnect = WinHttpConnect(hSession, Strings::to_utf16(hostname).c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0); + Checks::check_exit(VCPKG_LINE_INFO, hConnect, "WinHttpConnect() failed: %d", GetLastError()); + + // Create an HTTP request handle. + auto hRequest = WinHttpOpenRequest(hConnect, + L"GET", + Strings::to_utf16(url_path).c_str(), + nullptr, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_SECURE); + Checks::check_exit(VCPKG_LINE_INFO, hRequest, "WinHttpOpenRequest() failed: %d", GetLastError()); + + // Send a request. + auto bResults = + WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpSendRequest() failed: %d", GetLastError()); + + // End the request. + bResults = WinHttpReceiveResponse(hRequest, NULL); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReceiveResponse() failed: %d", GetLastError()); + + std::vector<char> buf; + + size_t total_downloaded_size = 0; + DWORD dwSize = 0; + do + { + DWORD downloaded_size = 0; + bResults = WinHttpQueryDataAvailable(hRequest, &dwSize); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpQueryDataAvailable() failed: %d", GetLastError()); + + if (buf.size() < dwSize) buf.resize(dwSize * 2); + + bResults = WinHttpReadData(hRequest, (LPVOID)buf.data(), dwSize, &downloaded_size); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReadData() failed: %d", GetLastError()); + fwrite(buf.data(), 1, downloaded_size, f); + + total_downloaded_size += downloaded_size; + } while (dwSize > 0); + + WinHttpCloseHandle(hSession); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hRequest); + fflush(f); + fclose(f); + } +#endif + + void verify_downloaded_file_hash(const Files::Filesystem& fs, + const std::string& url, + const fs::path& path, + const std::string& sha512) + { + std::string actual_hash = vcpkg::Hash::get_file_hash(fs, path, "SHA512"); + + // <HACK to handle NuGet.org changing nupkg hashes.> + // This is the NEW hash for 7zip + if (actual_hash == "a9dfaaafd15d98a2ac83682867ec5766720acf6e99d40d1a00d480692752603bf3f3742623f0ea85647a92374df" + "405f331afd6021c5cf36af43ee8db198129c0") + // This is the OLD hash for 7zip + actual_hash = "8c75314102e68d2b2347d592f8e3eb05812e1ebb525decbac472231633753f1d4ca31c8e6881a36144a8da26b257" + "1305b3ae3f4e2b85fc4a290aeda63d1a13b8"; + // </HACK> + + Checks::check_exit(VCPKG_LINE_INFO, + sha512 == actual_hash, + "File does not have the expected hash:\n" + " url : [ %s ]\n" + " File path : [ %s ]\n" + " Expected hash : [ %s ]\n" + " Actual hash : [ %s ]\n", + url, + path.u8string(), + sha512, + actual_hash); + } + + void download_file(vcpkg::Files::Filesystem& fs, + const std::string& url, + const fs::path& download_path, + const std::string& sha512) + { + const std::string download_path_part = download_path.u8string() + ".part"; + std::error_code ec; + fs.remove(download_path, ec); + fs.remove(download_path_part, ec); +#if defined(_WIN32) + auto url_no_proto = url.substr(8); // drop https:// + auto path_begin = Util::find(url_no_proto, '/'); + std::string hostname(url_no_proto.begin(), path_begin); + std::string path(path_begin, url_no_proto.end()); + + winhttp_download_file(fs, download_path_part.c_str(), hostname, path); +#else + const auto code = System::cmd_execute( + Strings::format(R"(curl -L '%s' --create-dirs --output '%s')", url, download_path_part)); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "Could not download %s", url); +#endif + + verify_downloaded_file_hash(fs, url, download_path_part, sha512); + fs.rename(download_path_part, download_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Failed to do post-download rename-in-place.\n" + "fs.rename(%s, %s, %s)", + download_path_part, + download_path.u8string(), + ec.message()); + } +} diff --git a/toolsrc/src/vcpkg/base/files.cpp b/toolsrc/src/vcpkg/base/files.cpp index 1723b467e..f9bce8631 100644 --- a/toolsrc/src/vcpkg/base/files.cpp +++ b/toolsrc/src/vcpkg/base/files.cpp @@ -4,10 +4,26 @@ #include <vcpkg/base/system.h> #include <vcpkg/base/util.h> +#if defined(__linux__) +#include <fcntl.h> +#include <sys/sendfile.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#endif + namespace vcpkg::Files { static const std::regex FILESYSTEM_INVALID_CHARACTERS_REGEX = std::regex(R"([\/:*?"<>|])"); + void Filesystem::write_contents(const fs::path& file_path, const std::string& data) + { + std::error_code ec; + write_contents(file_path, data, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "error while writing file: %s: %s", file_path.u8string(), ec.message()); + } + struct RealFilesystem final : Filesystem { virtual Expected<std::string> read_contents(const fs::path& file_path) const override @@ -55,17 +71,18 @@ namespace vcpkg::Files virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const std::string& filename) const override { + static const fs::path UNIX_ROOT = "/"; fs::path current_dir = starting_dir; - for (; !current_dir.empty(); current_dir = current_dir.parent_path()) + for (; !current_dir.empty() && current_dir != UNIX_ROOT; current_dir = current_dir.parent_path()) { const fs::path candidate = current_dir / filename; if (exists(candidate)) { - break; + return current_dir; } } - return current_dir; + return fs::path(); } virtual std::vector<fs::path> get_files_recursive(const fs::path& dir) const override @@ -116,6 +133,42 @@ namespace vcpkg::Files { fs::stdfs::rename(oldpath, newpath); } + virtual void rename_or_copy(const fs::path& oldpath, + const fs::path& newpath, + StringLiteral temp_suffix, + std::error_code& ec) override + { + this->rename(oldpath, newpath, ec); +#if defined(__linux__) + if (ec) + { + auto dst = newpath; + dst.replace_filename(dst.filename() + temp_suffix.c_str()); + + int i_fd = open(oldpath.c_str(), O_RDONLY); + if (i_fd == -1) return; + + int o_fd = creat(dst.c_str(), 0664); + if (o_fd == -1) + { + close(i_fd); + return; + } + + off_t bytes = 0; + struct stat info = {0}; + fstat(i_fd, &info); + auto written_bytes = sendfile(o_fd, i_fd, &bytes, info.st_size); + close(i_fd); + close(o_fd); + if (written_bytes == -1) return; + + this->rename(dst, newpath, ec); + if (ec) return; + this->remove(oldpath, ec); + } +#endif + } virtual bool remove(const fs::path& path) override { return fs::stdfs::remove(path); } 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 @@ -163,11 +216,19 @@ namespace vcpkg::Files { return fs::stdfs::copy_file(oldpath, newpath, opts, ec); } + virtual void copy_symlink(const fs::path& oldpath, const fs::path& newpath, std::error_code& ec) + { + return fs::stdfs::copy_symlink(oldpath, newpath, ec); + } virtual fs::file_status status(const fs::path& path, std::error_code& ec) const override { return fs::stdfs::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); + } virtual void write_contents(const fs::path& file_path, const std::string& data, std::error_code& ec) override { ec.clear(); @@ -196,6 +257,40 @@ namespace vcpkg::Files } } } + + virtual std::vector<fs::path> find_from_PATH(const std::string& name) const override + { +#if defined(_WIN32) + static constexpr StringLiteral EXTS[] = {".cmd", ".exe", ".bat"}; + auto paths = Strings::split(System::get_environment_variable("PATH").value_or_exit(VCPKG_LINE_INFO), ";"); + + std::vector<fs::path> ret; + 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)) + { + ret.push_back(p); + Debug::println("Found path: %s", p.u8string()); + } + } + } + + return ret; +#else + const std::string cmd = Strings::format("which %s", name); + auto out = System::cmd_execute_and_capture_output(cmd); + if (out.exit_code != 0) + { + return {}; + } + + return Util::fmap(Strings::split(out.output, "\n"), [](auto&& s) { return fs::path(s); }); +#endif + } }; Filesystem& get_real_filesystem() @@ -218,20 +313,4 @@ namespace vcpkg::Files } System::println(); } - - std::vector<fs::path> find_from_PATH(const std::string& name) - { -#if defined(_WIN32) - const std::string cmd = Strings::format("where.exe %s", name); -#else - const std::string cmd = Strings::format("which %s", name); -#endif - auto out = System::cmd_execute_and_capture_output(cmd); - if (out.exit_code != 0) - { - return {}; - } - - return Util::fmap(Strings::split(out.output, "\n"), [](auto&& s) { return fs::path(s); }); - } } diff --git a/toolsrc/src/vcpkg/commands.hash.cpp b/toolsrc/src/vcpkg/base/hash.cpp index 1f709f87b..7a74371db 100644 --- a/toolsrc/src/vcpkg/commands.hash.cpp +++ b/toolsrc/src/vcpkg/base/hash.cpp @@ -4,10 +4,16 @@ #include <vcpkg/base/strings.h> #include <vcpkg/base/system.h> #include <vcpkg/base/util.h> -#include <vcpkg/commands.h> -#include <vcpkg/help.h> -namespace vcpkg::Commands::Hash +#if defined(_WIN32) +#include <bcrypt.h> + +#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) { @@ -18,17 +24,7 @@ namespace vcpkg::Commands::Hash " % s", s); } -} - #if defined(_WIN32) -#include <bcrypt.h> - -#ifndef NT_SUCCESS -#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) -#endif - -namespace vcpkg::Commands::Hash -{ namespace { std::string to_hex(const unsigned char* string, const size_t bytes) @@ -154,10 +150,9 @@ namespace vcpkg::Commands::Hash }; } - std::string get_file_hash(const VcpkgPaths& paths, const fs::path& path, const std::string& hash_type) + std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type) { - Checks::check_exit( - VCPKG_LINE_INFO, paths.get_filesystem().exists(path), "File %s does not exist", path.u8string()); + Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string()); return BCryptHasher{hash_type}.hash_file(path); } @@ -166,11 +161,8 @@ namespace vcpkg::Commands::Hash verify_has_only_allowed_chars(s); return BCryptHasher{hash_type}.hash_string(s); } -} #else -namespace vcpkg::Commands::Hash -{ static std::string get_digest_size(const std::string& hash_type) { if (!Strings::case_insensitive_ascii_starts_with(hash_type, "SHA")) @@ -182,33 +174,45 @@ namespace vcpkg::Commands::Hash return hash_type.substr(3, hash_type.length() - 3); } - static std::string run_shasum_and_post_process(const std::string& cmd_line) + static std::string parse_shasum_output(const std::string& shasum_output) { - const auto ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, - ec_data.exit_code == 0, - "Failed to run:\n" - " %s", - cmd_line); - - std::vector<std::string> split = Strings::split(ec_data.output, " "); + 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)", - ec_data.output, + shasum_output, std::to_string(split.size())); return split[0]; } - std::string get_file_hash(const VcpkgPaths& paths, const fs::path& path, const std::string& hash_type) + 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, paths.get_filesystem().exists(path), "File %s does not exist", path.u8string()); - const std::string cmd_line = Strings::format(R"(shasum -a %s "%s")", digest_size, path.u8string()); - return run_shasum_and_post_process(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string()); + + // Try hash-specific tools, like sha512sum + { + 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) + { + return parse_shasum_output(ec_data.output); + } + } + + // Try shasum + { + 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) + { + return parse_shasum_output(ec_data.output); + } + } + + Checks::exit_with_message(VCPKG_LINE_INFO, "Could not hash file %s with %s", path.u8string(), hash_type); } std::string get_string_hash(const std::string& s, const std::string& hash_type) @@ -216,31 +220,27 @@ namespace vcpkg::Commands::Hash const std::string digest_size = get_digest_size(hash_type); verify_has_only_allowed_chars(s); - const std::string cmd_line = Strings::format(R"(echo -n "%s" | shasum -a %s)", s, digest_size); - return run_shasum_and_post_process(cmd_line); - } -} -#endif + // Try hash-specific tools, like sha512sum + { + 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) + { + return parse_shasum_output(ec_data.output); + } + } -namespace vcpkg::Commands::Hash -{ - const CommandStructure COMMAND_STRUCTURE = { - Strings::format("The argument should be a file path\n%s", - Help::create_example_string("hash boost_1_62_0.tar.bz2")), - 1, - 2, - {}, - nullptr, - }; - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + // Try shasum + { + 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); + } + } - const fs::path file_to_hash = args.command_arguments[0]; - const std::string algorithm = args.command_arguments.size() == 2 ? args.command_arguments[1] : "SHA512"; - const std::string hash = get_file_hash(paths, file_to_hash, algorithm); - System::println(hash); - Checks::exit_success(VCPKG_LINE_INFO); + Checks::exit_with_message(VCPKG_LINE_INFO, "Could not hash input string with %s", hash_type); } +#endif } diff --git a/toolsrc/src/vcpkg/base/stringrange.cpp b/toolsrc/src/vcpkg/base/stringrange.cpp new file mode 100644 index 000000000..f7e431c88 --- /dev/null +++ b/toolsrc/src/vcpkg/base/stringrange.cpp @@ -0,0 +1,79 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/stringrange.h> + +namespace vcpkg +{ + std::vector<StringRange> StringRange::find_all_enclosed(const StringRange& input, + const std::string& left_delim, + const std::string& right_delim) + { + std::string::const_iterator it_left = input.begin; + std::string::const_iterator it_right = input.begin; + + std::vector<StringRange> output; + + while (true) + { + it_left = std::search(it_right, input.end, left_delim.cbegin(), left_delim.cend()); + if (it_left == input.end) break; + + it_left += left_delim.length(); + + it_right = std::search(it_left, input.end, right_delim.cbegin(), right_delim.cend()); + if (it_right == input.end) break; + + output.emplace_back(it_left, it_right); + + ++it_right; + } + + return output; + } + + StringRange StringRange::find_exactly_one_enclosed(const StringRange& input, + const std::string& left_tag, + const std::string& right_tag) + { + std::vector<StringRange> result = find_all_enclosed(input, left_tag, right_tag); + Checks::check_exit(VCPKG_LINE_INFO, + result.size() == 1, + "Found %d sets of %s.*%s but expected exactly 1, in block:\n%s", + result.size(), + left_tag, + right_tag, + input); + return result.front(); + } + + Optional<StringRange> StringRange::find_at_most_one_enclosed(const StringRange& input, + const std::string& left_tag, + const std::string& right_tag) + { + std::vector<StringRange> result = find_all_enclosed(input, left_tag, right_tag); + Checks::check_exit(VCPKG_LINE_INFO, + result.size() <= 1, + "Found %d sets of %s.*%s but expected at most 1, in block:\n%s", + result.size(), + left_tag, + right_tag, + input); + + if (result.empty()) + { + return nullopt; + } + + return result.front(); + } + + StringRange::StringRange(const std::string& s) : begin(s.cbegin()), end(s.cend()) {} + + StringRange::StringRange(const std::string::const_iterator begin, const std::string::const_iterator end) + : begin(begin), end(end) + { + } + + std::string StringRange::to_string() const { return std::string(this->begin, this->end); } +} diff --git a/toolsrc/src/vcpkg/base/strings.cpp b/toolsrc/src/vcpkg/base/strings.cpp index fbc33ca42..8d43e7af7 100644 --- a/toolsrc/src/vcpkg/base/strings.cpp +++ b/toolsrc/src/vcpkg/base/strings.cpp @@ -49,33 +49,29 @@ namespace vcpkg::Strings::details namespace vcpkg::Strings { +#if defined(_WIN32) std::wstring to_utf16(const CStringView& s) { -#if defined(_WIN32) std::wstring output; const size_t size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0); if (size == 0) return output; output.resize(size - 1); MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, output.data(), static_cast<int>(size) - 1); return output; -#else - Checks::unreachable(VCPKG_LINE_INFO); -#endif } +#endif +#if defined(_WIN32) std::string to_utf8(const wchar_t* w) { -#if defined(_WIN32) std::string output; const size_t size = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); if (size == 0) return output; output.resize(size - 1); WideCharToMultiByte(CP_UTF8, 0, w, -1, output.data(), static_cast<int>(size) - 1, nullptr, nullptr); return output; -#else - Checks::unreachable(VCPKG_LINE_INFO); -#endif } +#endif std::string escape_string(const CStringView& s, char char_to_escape, char escape_char) { @@ -132,6 +128,12 @@ namespace vcpkg::Strings #endif } + bool ends_with(const std::string& s, StringLiteral pattern) + { + if (s.size() < pattern.size()) return false; + return std::equal(s.end() - pattern.size(), s.end(), pattern.c_str(), pattern.c_str() + pattern.size()); + } + std::string replace_all(std::string&& s, const std::string& search, const std::string& rep) { size_t pos = 0; diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp index d4210fe6d..9c72f8401 100644 --- a/toolsrc/src/vcpkg/base/system.cpp +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -19,19 +19,6 @@ namespace vcpkg::System { - tm get_current_date_time() - { - using std::chrono::system_clock; - std::time_t now_time = system_clock::to_time_t(system_clock::now()); - tm parts{}; -#if defined(_WIN32) - localtime_s(&parts, &now_time); -#else - parts = *localtime(&now_time); -#endif - return parts; - } - fs::path get_exe_path_of_current_process() { #if defined(_WIN32) @@ -40,8 +27,9 @@ namespace vcpkg::System if (bytes == 0) std::abort(); return fs::path(buf, buf + bytes); #elif defined(__APPLE__) - uint32_t size = 1024 * 32; - char buf[size] = {}; + static constexpr const uint32_t buff_size = 1024 * 32; + uint32_t size = buff_size; + char buf[buff_size] = {}; bool result = _NSGetExecutablePath(buf, &size); Checks::check_exit(VCPKG_LINE_INFO, result != -1, "Could not determine current executable path."); std::unique_ptr<char> canonicalPath(realpath(buf, NULL)); @@ -128,34 +116,9 @@ namespace vcpkg::System R"("%s" %s -P "%s")", cmake_exe.u8string(), cmd_cmake_pass_variables, cmake_script.generic_u8string()); } - PowershellParameter::PowershellParameter(const CStringView varname, const char* varvalue) - : s(Strings::format(R"(-%s '%s')", varname, varvalue)) - { - } - - PowershellParameter::PowershellParameter(const CStringView varname, const std::string& varvalue) - : PowershellParameter(varname, varvalue.c_str()) - { - } - - PowershellParameter::PowershellParameter(const CStringView varname, const fs::path& path) - : PowershellParameter(varname, path.generic_u8string()) - { - } - - static std::string make_powershell_cmd(const fs::path& script_path, - const std::vector<PowershellParameter>& parameters) - { - const std::string args = Strings::join(" ", parameters, [](auto&& v) { return v.s; }); - - // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned - return Strings::format( - R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' %s}")", script_path.u8string(), args); - } - - int cmd_execute_clean(const CStringView cmd_line, const std::unordered_map<std::string, std::string>& extra_env) - { #if defined(_WIN32) + static std::wstring compute_clean_environment(const std::unordered_map<std::string, std::string>& extra_env) + { 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( @@ -205,11 +168,10 @@ namespace vcpkg::System L"CUDA_PATH", // Environmental variable generated automatically by CUDA after installation L"NVCUDASAMPLES_ROOT", + // Enables find_package(Vulkan) in CMake. Environmental variable generated by Vulkan SDK installer + L"VULKAN_SDK", }; - // Flush stdout before launching external process - fflush(nullptr); - std::wstring env_cstr; for (auto&& env_wstring : env_wstrings) @@ -240,14 +202,28 @@ namespace vcpkg::System env_cstr.push_back(L'\0'); } + return env_cstr; + } +#endif + +#if defined(_WIN32) + /// <param name="maybe_environment">If non-null, an environment block to use for the new process. If null, the new + /// process will inherit the current environment.</param> + static void windows_create_process(const CStringView cmd_line, + const wchar_t* maybe_environment, + DWORD dwCreationFlags, + PROCESS_INFORMATION* process_info) noexcept + { + Checks::check_exit(VCPKG_LINE_INFO, process_info != nullptr); + STARTUPINFOW startup_info; memset(&startup_info, 0, sizeof(STARTUPINFOW)); startup_info.cb = sizeof(STARTUPINFOW); - PROCESS_INFORMATION process_info; - memset(&process_info, 0, sizeof(PROCESS_INFORMATION)); + // Flush stdout before launching external process + fflush(nullptr); - // Basically we are wrapping it in quotes + // Wrapping the command in a single set of quotes causes cmd.exe to correctly execute const std::string actual_cmd_line = Strings::format(R"###(cmd.exe /c "%s")###", cmd_line); Debug::println("CreateProcessW(%s)", actual_cmd_line); bool succeeded = TRUE == CreateProcessW(nullptr, @@ -255,40 +231,81 @@ namespace vcpkg::System nullptr, nullptr, FALSE, - IDLE_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, - env_cstr.data(), + IDLE_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT | dwCreationFlags, + (void*)maybe_environment, nullptr, &startup_info, - &process_info); + process_info); Checks::check_exit(VCPKG_LINE_INFO, succeeded, "Process creation failed with error code: %lu", GetLastError()); + } +#endif + +#if defined(_WIN32) + void cmd_execute_no_wait(const CStringView cmd_line) noexcept + { + auto timer = Chrono::ElapsedTimer::create_started(); + + PROCESS_INFORMATION process_info; + memset(&process_info, 0, sizeof(PROCESS_INFORMATION)); + + windows_create_process(cmd_line, nullptr, DETACHED_PROCESS, &process_info); + + CloseHandle(process_info.hThread); + CloseHandle(process_info.hProcess); + + Debug::println("CreateProcessW() took %d us", static_cast<int>(timer.microseconds())); + } +#endif + + int cmd_execute_clean(const CStringView cmd_line, + const std::unordered_map<std::string, std::string>& extra_env) noexcept + { + auto timer = Chrono::ElapsedTimer::create_started(); +#if defined(_WIN32) + + PROCESS_INFORMATION process_info; + memset(&process_info, 0, sizeof(PROCESS_INFORMATION)); + + GlobalState::g_ctrl_c_state.transition_to_spawn_process(); + auto clean_env = compute_clean_environment(extra_env); + windows_create_process(cmd_line, clean_env.c_str(), NULL, &process_info); CloseHandle(process_info.hThread); const DWORD result = WaitForSingleObject(process_info.hProcess, INFINITE); + GlobalState::g_ctrl_c_state.transition_from_spawn_process(); Checks::check_exit(VCPKG_LINE_INFO, result != WAIT_FAILED, "WaitForSingleObject failed"); DWORD exit_code = 0; GetExitCodeProcess(process_info.hProcess, &exit_code); - Debug::println("CreateProcessW() returned %lu", exit_code); + CloseHandle(process_info.hProcess); + + Debug::println("CreateProcessW() returned %lu after %d us", exit_code, static_cast<int>(timer.microseconds())); + return static_cast<int>(exit_code); #else + Debug::println("system(%s)", cmd_line.c_str()); fflush(nullptr); - return system(cmd_line.c_str()); + int rc = system(cmd_line.c_str()); + Debug::println("system() returned %d after %d us", rc, static_cast<int>(timer.microseconds())); + return rc; #endif } - int cmd_execute(const CStringView cmd_line) + int cmd_execute(const CStringView cmd_line) noexcept { // Flush stdout before launching external process fflush(nullptr); - // Basically we are wrapping it in quotes #if defined(_WIN32) + // We are wrap the command line in quotes to cause cmd.exe to correctly process it const std::string& actual_cmd_line = Strings::format(R"###("%s")###", cmd_line); Debug::println("_wsystem(%s)", actual_cmd_line); + GlobalState::g_ctrl_c_state.transition_to_spawn_process(); const int exit_code = _wsystem(Strings::to_utf16(actual_cmd_line).c_str()); + GlobalState::g_ctrl_c_state.transition_from_spawn_process(); Debug::println("_wsystem() returned %d", exit_code); #else Debug::println("_system(%s)", cmd_line); @@ -298,11 +315,8 @@ namespace vcpkg::System return exit_code; } - ExitCodeAndOutput cmd_execute_and_capture_output(const CStringView cmd_line) + ExitCodeAndOutput cmd_execute_and_capture_output(const CStringView cmd_line) noexcept { - // Flush stdout before launching external process - fflush(stdout); - auto timer = Chrono::ElapsedTimer::create_started(); #if defined(_WIN32) @@ -311,9 +325,13 @@ namespace vcpkg::System Debug::println("_wpopen(%s)", actual_cmd_line); std::wstring output; wchar_t buf[1024]; + GlobalState::g_ctrl_c_state.transition_to_spawn_process(); + // Flush stdout before launching external process + fflush(stdout); const auto pipe = _wpopen(Strings::to_utf16(actual_cmd_line).c_str(), L"r"); if (pipe == nullptr) { + GlobalState::g_ctrl_c_state.transition_from_spawn_process(); return {1, Strings::to_utf8(output.c_str())}; } while (fgetws(buf, 1024, pipe)) @@ -322,10 +340,12 @@ namespace vcpkg::System } if (!feof(pipe)) { + GlobalState::g_ctrl_c_state.transition_from_spawn_process(); return {1, Strings::to_utf8(output.c_str())}; } const auto ec = _pclose(pipe); + GlobalState::g_ctrl_c_state.transition_from_spawn_process(); // On Win7, output from powershell calls contain a utf-8 byte order mark in the utf-16 stream, so we strip it // out if it is present. 0xEF,0xBB,0xBF is the UTF-8 byte-order mark @@ -344,6 +364,8 @@ namespace vcpkg::System Debug::println("popen(%s)", actual_cmd_line); std::string output; char buf[1024]; + // Flush stdout before launching external process + fflush(stdout); const auto pipe = popen(actual_cmd_line.c_str(), "r"); if (pipe == nullptr) { @@ -366,71 +388,6 @@ namespace vcpkg::System #endif } - void powershell_execute(const std::string& title, - const fs::path& script_path, - const std::vector<PowershellParameter>& parameters) - { - const std::string cmd = make_powershell_cmd(script_path, parameters); - const int rc = System::cmd_execute(cmd); - - if (rc) - { - System::println(Color::error, - "%s\n" - "Could not run:\n" - " '%s'", - title, - script_path.generic_string()); - - { - auto locked_metrics = Metrics::g_metrics.lock(); - locked_metrics->track_property("error", "powershell script failed"); - locked_metrics->track_property("title", title); - } - - Checks::exit_with_code(VCPKG_LINE_INFO, rc); - } - } - - std::string powershell_execute_and_capture_output(const std::string& title, - const fs::path& script_path, - const std::vector<PowershellParameter>& parameters) - { - const std::string cmd = make_powershell_cmd(script_path, parameters); - auto rc = System::cmd_execute_and_capture_output(cmd); - - if (rc.exit_code) - { - System::println(Color::error, - "%s\n" - "Could not run:\n" - " '%s'\n" - "Error message was:\n" - " %s", - title, - script_path.generic_string(), - rc.output); - - { - auto locked_metrics = Metrics::g_metrics.lock(); - locked_metrics->track_property("error", "powershell script failed"); - locked_metrics->track_property("title", title); - } - - Checks::exit_with_code(VCPKG_LINE_INFO, rc.exit_code); - } - - // Remove newline from all output. - // Powershell returns newlines when it hits the column count of the console. - // For example, this is 80 in cmd on Windows 7. If the expected output is longer than 80 lines, we get - // newlines in-between the data. - // To solve this, we design our interaction with powershell to not depend on newlines, - // and then strip all newlines here. - rc.output = Strings::replace_all(std::move(rc.output), "\n", ""); - - return rc.output; - } - void println() { putchar('\n'); } void print(const CStringView message) { fputs(message.c_str(), stdout); } @@ -446,7 +403,7 @@ namespace vcpkg::System #if defined(_WIN32) const HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{}; + CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info {}; GetConsoleScreenBufferInfo(console_handle, &console_screen_buffer_info); const auto original_color = console_screen_buffer_info.wAttributes; diff --git a/toolsrc/src/vcpkg/build.cpp b/toolsrc/src/vcpkg/build.cpp index 7a9d35667..0e60fd50e 100644 --- a/toolsrc/src/vcpkg/build.cpp +++ b/toolsrc/src/vcpkg/build.cpp @@ -3,9 +3,11 @@ #include <vcpkg/base/checks.h> #include <vcpkg/base/chrono.h> #include <vcpkg/base/enums.h> +#include <vcpkg/base/hash.h> #include <vcpkg/base/optional.h> #include <vcpkg/base/stringliteral.h> #include <vcpkg/base/system.h> + #include <vcpkg/build.h> #include <vcpkg/commands.h> #include <vcpkg/dependencies.h> @@ -61,11 +63,15 @@ namespace vcpkg::Build::Command spec.name()); const StatusParagraphs status_db = database_load_check(paths); - const Build::BuildPackageOptions build_package_options{Build::UseHeadVersion::NO, - Build::AllowDownloads::YES, - Build::CleanBuildtrees::NO, - Build::CleanPackages::NO, - Build::DownloadTool::BUILT_IN}; + const Build::BuildPackageOptions build_package_options{ + Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN, + GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, + Build::FailOnTombstone::NO, + }; std::set<std::string> features_as_set(full_spec.features.begin(), full_spec.features.end()); features_as_set.emplace("core"); @@ -217,7 +223,12 @@ namespace vcpkg::Build if (it != toolset.supported_architectures.end()) return it->name; } - Checks::exit_with_message(VCPKG_LINE_INFO, "Unsupported toolchain combination %s", target_architecture); + Checks::exit_with_message(VCPKG_LINE_INFO, + "Unsupported toolchain combination. Target was: %s but supported ones were:\n%s", + target_architecture, + Strings::join(",", toolset.supported_architectures, [](const ToolsetArchOption& t) { + return t.name.c_str(); + })); } std::string make_build_env_cmd(const PreBuildInfo& pre_build_info, const Toolset& toolset) @@ -234,7 +245,7 @@ namespace vcpkg::Build const auto arch = to_vcvarsall_toolchain(pre_build_info.target_architecture, toolset); const auto target = to_vcvarsall_target(pre_build_info.cmake_system_name); - return Strings::format(R"("%s" %s %s %s %s 2>&1)", + return Strings::format(R"("%s" %s %s %s %s 2>&1 <NUL)", toolset.vcvarsall.u8string(), Strings::join(" ", toolset.vcvarsall_options), arch, @@ -357,7 +368,7 @@ namespace vcpkg::Build { {"CMD", "BUILD"}, {"PORT", config.scf.core_paragraph->name}, - {"CURRENT_PORT_DIR", config.port_dir / "/."}, + {"CURRENT_PORT_DIR", config.port_dir}, {"TARGET_TRIPLET", spec.triplet().canonical_name()}, {"VCPKG_PLATFORM_TOOLSET", toolset.version.c_str()}, {"VCPKG_USE_HEAD_VERSION", @@ -370,9 +381,15 @@ namespace vcpkg::Build }); auto command = make_build_env_cmd(pre_build_info, toolset); - if (!command.empty()) command.append(" && "); + if (!command.empty()) + { +#ifdef _WIN32 + command.append(" & "); +#else + command.append(" && "); +#endif + } command.append(cmd_launch_cmake); - const auto timer = Chrono::ElapsedTimer::create_started(); const int return_code = System::cmd_execute_clean(command); @@ -429,7 +446,7 @@ namespace vcpkg::Build auto buildtree_files = fs.get_files_non_recursive(buildtrees_dir); for (auto&& file : buildtree_files) { - if (fs.is_directory(file) && file.filename() != "src") + if (fs.is_directory(file)) // Will only keep the logs { std::error_code ec; fs.remove_all(file, ec); @@ -445,20 +462,22 @@ namespace vcpkg::Build const PreBuildInfo& pre_build_info, Span<const AbiEntry> dependency_abis) { - if (!GlobalState::g_binary_caching) return nullopt; + 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; + std::vector<AbiEntry> abi_tag_entries(dependency_abis.begin(), dependency_abis.end()); - abi_tag_entries.insert(abi_tag_entries.end(), dependency_abis.begin(), dependency_abis.end()); + abi_tag_entries.emplace_back(AbiEntry{"cmake", paths.get_tool_version(Tools::CMAKE)}); abi_tag_entries.emplace_back( - AbiEntry{"portfile", Commands::Hash::get_file_hash(paths, config.port_dir / "portfile.cmake", "SHA1")}); + AbiEntry{"portfile", vcpkg::Hash::get_file_hash(fs, config.port_dir / "portfile.cmake", "SHA1")}); abi_tag_entries.emplace_back( - AbiEntry{"control", Commands::Hash::get_file_hash(paths, config.port_dir / "CONTROL", "SHA1")}); + AbiEntry{"control", vcpkg::Hash::get_file_hash(fs, config.port_dir / "CONTROL", "SHA1")}); + + abi_tag_entries.emplace_back(AbiEntry{"vcpkg_fixup_cmake_targets", "1"}); abi_tag_entries.emplace_back(AbiEntry{"triplet", pre_build_info.triplet_abi_tag}); @@ -484,7 +503,7 @@ namespace vcpkg::Build } auto abi_tag_entries_missing = abi_tag_entries; - Util::stable_keep_if(abi_tag_entries_missing, [](const AbiEntry& p) { return p.value.empty(); }); + Util::erase_remove_if(abi_tag_entries_missing, [](const AbiEntry& p) { return !p.value.empty(); }); if (abi_tag_entries_missing.empty()) { @@ -493,7 +512,7 @@ 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); - return AbiTagAndFile{Commands::Hash::get_file_hash(paths, abi_file_path, "SHA1"), abi_file_path}; + return AbiTagAndFile{Hash::get_file_hash(fs, abi_file_path, "SHA1"), abi_file_path}; } System::println( @@ -520,8 +539,8 @@ namespace vcpkg::Build System::cmd_execute_clean(Strings::format( R"("%s" x "%s" -o"%s" -y >nul)", seven_zip_exe.u8string(), archive_path.u8string(), pkg_path.u8string())); #else - System::cmd_execute_clean(Strings::format( - R"(unzip -qq "%s" "-d%s")", archive_path.u8string(), pkg_path.u8string())); + System::cmd_execute_clean( + Strings::format(R"(unzip -qq "%s" "-d%s")", archive_path.u8string(), pkg_path.u8string())); #endif } @@ -537,11 +556,10 @@ namespace vcpkg::Build #if defined(_WIN32) auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP); - System::cmd_execute_clean(Strings::format( - R"("%s" a "%s" "%s\*" >nul)", - seven_zip_exe.u8string(), - tmp_archive_path.u8string(), - paths.package_dir(spec).u8string())); + System::cmd_execute_clean(Strings::format(R"("%s" a "%s" "%s\*" >nul)", + seven_zip_exe.u8string(), + tmp_archive_path.u8string(), + paths.package_dir(spec).u8string())); #else System::cmd_execute_clean(Strings::format( R"(cd '%s' && zip --quiet -r '%s' *)", paths.package_dir(spec).u8string(), tmp_archive_path.u8string())); @@ -563,8 +581,8 @@ namespace vcpkg::Build Util::sort_unique_erase(dep_pspecs); // Find all features that aren't installed. This mutates required_fspecs. - Util::unstable_keep_if(required_fspecs, [&](FeatureSpec const& fspec) { - return !status_db.is_installed(fspec) && fspec.name() != name; + Util::erase_remove_if(required_fspecs, [&](FeatureSpec const& fspec) { + return status_db.is_installed(fspec) || fspec.name() == name; }); if (!required_fspecs.empty()) @@ -593,7 +611,7 @@ namespace vcpkg::Build const auto abi_tag_and_file = maybe_abi_tag_and_file.get(); - if (GlobalState::g_binary_caching && abi_tag_and_file) + 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"; @@ -615,8 +633,16 @@ namespace vcpkg::Build if (fs.exists(archive_tombstone_path)) { - System::println("Found failure tombstone: %s", archive_tombstone_path.u8string()); - return BuildResult::BUILD_FAILED; + if (config.build_package_options.fail_on_tombstone == FailOnTombstone::YES) + { + System::println("Found failure tombstone: %s", archive_tombstone_path.u8string()); + return BuildResult::BUILD_FAILED; + } + else + { + System::println( + System::Color::warning, "Found failure tombstone: %s", archive_tombstone_path.u8string()); + } } System::println("Could not locate cached archive: %s", archive_path.u8string()); @@ -637,10 +663,14 @@ namespace vcpkg::Build compress_archive(paths, spec, tmp_archive_path); fs.create_directories(archive_path.parent_path(), ec); - fs.rename(tmp_archive_path, archive_path, ec); + fs.rename_or_copy(tmp_archive_path, archive_path, ".tmp", ec); if (ec) - System::println( - System::Color::warning, "Failed to store binary cache: %s", archive_path.u8string()); + { + System::println(System::Color::warning, + "Failed to store binary cache %s: %s", + archive_path.u8string(), + ec.message()); + } else System::println("Stored binary cache: %s", archive_path.u8string()); } @@ -769,26 +799,6 @@ namespace vcpkg::Build 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 std::string triplet_abi_tag = [&]() { - static std::map<fs::path, std::string> s_hash_cache; - - if (GlobalState::g_binary_caching) - { - auto it_hash = s_hash_cache.find(triplet_file_path); - if (it_hash != s_hash_cache.end()) - { - return it_hash->second; - } - auto hash = Commands::Hash::get_file_hash(paths, triplet_file_path, "SHA1"); - s_hash_cache.emplace(triplet_file_path, hash); - return hash; - } - else - { - return std::string(); - } - }(); - const auto cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path, ports_cmake_script_path, { @@ -800,7 +810,6 @@ namespace vcpkg::Build const std::vector<std::string> lines = Strings::split(ec_data.output, "\n"); PreBuildInfo pre_build_info; - pre_build_info.triplet_abi_tag = triplet_abi_tag; const auto e = lines.cend(); auto cur = std::find(lines.cbegin(), e, FLAG_GUID); @@ -876,6 +885,47 @@ namespace vcpkg::Build 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; + }(); + return pre_build_info; } ExtendedBuildResult::ExtendedBuildResult(BuildResult code) : code(code) {} diff --git a/toolsrc/src/vcpkg/commands.autocomplete.cpp b/toolsrc/src/vcpkg/commands.autocomplete.cpp index 564404421..3b353feec 100644 --- a/toolsrc/src/vcpkg/commands.autocomplete.cpp +++ b/toolsrc/src/vcpkg/commands.autocomplete.cpp @@ -40,26 +40,25 @@ namespace vcpkg::Commands::Autocomplete const std::string requested_command = match[1].str(); // First try public commands - std::vector<std::string> public_commands = { - "install", - "search", - "remove", - "list", - "update", - "hash", - "help", - "integrate", - "export", - "edit", - "create", - "owns", - "cache", - "version", - "contact", - }; - - Util::unstable_keep_if(public_commands, [&](const std::string& s) { - return Strings::case_insensitive_ascii_starts_with(s, requested_command); + std::vector<std::string> public_commands = {"install", + "search", + "remove", + "list", + "update", + "hash", + "help", + "integrate", + "export", + "edit", + "create", + "owns", + "cache", + "version", + "contact", + "upgrade"}; + + Util::erase_remove_if(public_commands, [&](const std::string& s) { + return !Strings::case_insensitive_ascii_starts_with(s, requested_command); }); if (!public_commands.empty()) @@ -78,8 +77,8 @@ namespace vcpkg::Commands::Autocomplete "portsdiff", }; - Util::unstable_keep_if(private_commands, [&](const std::string& s) { - return Strings::case_insensitive_ascii_starts_with(s, requested_command); + Util::erase_remove_if(private_commands, [&](const std::string& s) { + return !Strings::case_insensitive_ascii_starts_with(s, requested_command); }); output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(private_commands)); @@ -98,8 +97,8 @@ namespace vcpkg::Commands::Autocomplete } std::vector<std::string> triplets = paths.get_available_triplets(); - Util::unstable_keep_if(triplets, [&](const std::string& s) { - return Strings::case_insensitive_ascii_starts_with(s, triplet_prefix); + Util::erase_remove_if(triplets, [&](const std::string& s) { + return !Strings::case_insensitive_ascii_starts_with(s, triplet_prefix); }); auto result = combine_port_with_triplets(port_name, triplets); @@ -124,6 +123,7 @@ namespace vcpkg::Commands::Autocomplete CommandEntry{"edit", R"###(^edit\s(.*\s|)(\S*)$)###", Edit::COMMAND_STRUCTURE}, CommandEntry{"remove", R"###(^remove\s(.*\s|)(\S*)$)###", Remove::COMMAND_STRUCTURE}, CommandEntry{"integrate", R"###(^integrate(\s+)(\S*)$)###", Integrate::COMMAND_STRUCTURE}, + CommandEntry{"upgrade", R"###(^upgrade(\s+)(\S*)$)###", Upgrade::COMMAND_STRUCTURE}, }; for (auto&& command : COMMANDS) @@ -150,8 +150,8 @@ namespace vcpkg::Commands::Autocomplete } } - Util::unstable_keep_if(results, [&](const std::string& s) { - return Strings::case_insensitive_ascii_starts_with(s, prefix); + Util::erase_remove_if(results, [&](const std::string& s) { + return !Strings::case_insensitive_ascii_starts_with(s, prefix); }); if (command.name == "install" && results.size() == 1 && !is_option) diff --git a/toolsrc/src/vcpkg/commands.cache.cpp b/toolsrc/src/vcpkg/commands.cache.cpp index a9d8ba03c..464f4f9ee 100644 --- a/toolsrc/src/vcpkg/commands.cache.cpp +++ b/toolsrc/src/vcpkg/commands.cache.cpp @@ -61,7 +61,7 @@ namespace vcpkg::Commands::Cache for (const BinaryParagraph& binary_paragraph : binary_paragraphs) { const std::string displayname = binary_paragraph.displayname(); - if (Strings::case_insensitive_ascii_find(displayname, args.command_arguments[0]) == displayname.end()) + if (!Strings::case_insensitive_ascii_contains(displayname, args.command_arguments[0])) { continue; } diff --git a/toolsrc/src/vcpkg/commands.ci.cpp b/toolsrc/src/vcpkg/commands.ci.cpp index e2b93dc7e..551eee27b 100644 --- a/toolsrc/src/vcpkg/commands.ci.cpp +++ b/toolsrc/src/vcpkg/commands.ci.cpp @@ -28,6 +28,7 @@ namespace vcpkg::Commands::CI static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; static constexpr StringLiteral OPTION_EXCLUDE = "--exclude"; + static constexpr StringLiteral OPTION_PURGE_TOMBSTONES = "--purge-tombstones"; static constexpr StringLiteral OPTION_XUNIT = "--x-xunit"; static constexpr std::array<CommandSetting, 2> CI_SETTINGS = {{ @@ -35,8 +36,10 @@ namespace vcpkg::Commands::CI {OPTION_XUNIT, "File to output results in XUnit format (internal)"}, }}; - static constexpr std::array<CommandSwitch, 1> CI_SWITCHES = { - {{OPTION_DRY_RUN, "Print out plan without execution"}}}; + static constexpr std::array<CommandSwitch, 2> CI_SWITCHES = {{ + {OPTION_DRY_RUN, "Print out plan without execution"}, + {OPTION_PURGE_TOMBSTONES, "Purge failure tombstones and retry building the ports"}, + }}; const CommandStructure COMMAND_STRUCTURE = { Help::create_example_string("ci x64-windows"), @@ -46,10 +49,18 @@ namespace vcpkg::Commands::CI nullptr, }; - UnknownCIPortsResults find_unknown_ports_for_ci(const VcpkgPaths& paths, - const std::set<std::string>& exclusions, - const Dependencies::PortFileProvider& provider, - const std::vector<FeatureSpec>& fspecs) + struct UnknownCIPortsResults + { + std::vector<FullPackageSpec> unknown; + std::map<PackageSpec, Build::BuildResult> known; + std::map<PackageSpec, std::vector<std::string>> features; + }; + + static UnknownCIPortsResults find_unknown_ports_for_ci(const VcpkgPaths& paths, + const std::set<std::string>& exclusions, + const Dependencies::PortFileProvider& provider, + const std::vector<FeatureSpec>& fspecs, + const bool purge_tombstones) { UnknownCIPortsResults ret; @@ -58,14 +69,19 @@ namespace vcpkg::Commands::CI std::map<PackageSpec, std::string> abi_tag_map; std::set<PackageSpec> will_fail; - const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, - Build::AllowDownloads::YES, - Build::CleanBuildtrees::YES, - Build::CleanPackages::YES}; + const Build::BuildPackageOptions install_plan_options = { + Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::YES, + Build::CleanPackages::YES, + Build::DownloadTool::BUILT_IN, + GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, + Build::FailOnTombstone::YES, + }; vcpkg::Cache<Triplet, Build::PreBuildInfo> pre_build_info_cache; - auto action_plan = Dependencies::create_feature_install_plan(provider, fspecs, StatusParagraphs{}); + auto action_plan = Dependencies::create_feature_install_plan(provider, fspecs, StatusParagraphs {}); for (auto&& action : action_plan) { @@ -77,7 +93,7 @@ namespace vcpkg::Commands::CI { auto triplet = p->spec.triplet(); - const Build::BuildPackageConfig build_config{ + const Build::BuildPackageConfig build_config { *scf, triplet, paths.port_dir(p->spec), install_plan_options, p->feature_list}; auto dependency_abis = @@ -114,8 +130,17 @@ namespace vcpkg::Commands::CI auto archive_path = archives_root_dir / archive_subpath; auto archive_tombstone_path = archives_root_dir / "fail" / archive_subpath; + if (purge_tombstones) + { + std::error_code ec; + fs.remove(archive_tombstone_path, ec); // Ignore error + } + bool b_will_build = false; + ret.features.emplace(p->spec, + std::vector<std::string> {p->feature_list.begin(), p->feature_list.end()}); + if (Util::Sets::contains(exclusions, p->spec.name())) { ret.known.emplace(p->spec, BuildResult::EXCLUDED); @@ -141,7 +166,7 @@ namespace vcpkg::Commands::CI } else { - ret.unknown.push_back(p->spec); + ret.unknown.push_back({p->spec, {p->feature_list.begin(), p->feature_list.end()}}); b_will_build = true; } @@ -169,7 +194,8 @@ namespace vcpkg::Commands::CI exclusions_set.insert(exclusions.begin(), exclusions.end()); } - auto is_dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); + const auto is_dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); + const auto purge_tombstones = Util::Sets::contains(options.switches, OPTION_PURGE_TOMBSTONES); std::vector<Triplet> triplets; for (const std::string& triplet : args.command_arguments) @@ -185,10 +211,15 @@ namespace vcpkg::Commands::CI StatusParagraphs status_db = database_load_check(paths); const auto& paths_port_file = Dependencies::PathsPortFileProvider(paths); - const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, - Build::AllowDownloads::YES, - Build::CleanBuildtrees::YES, - Build::CleanPackages::YES}; + const Build::BuildPackageOptions install_plan_options = { + Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::YES, + Build::CleanPackages::YES, + Build::DownloadTool::BUILT_IN, + GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, + Build::FailOnTombstone::YES, + }; std::vector<std::map<PackageSpec, BuildResult>> all_known_results; @@ -198,25 +229,48 @@ namespace vcpkg::Commands::CI { Input::check_triplet(triplet, paths); + Dependencies::PackageGraph pgraph(paths_port_file, status_db); + std::vector<PackageSpec> specs = PackageSpec::to_package_specs(all_ports, triplet); // Install the default features for every package auto all_fspecs = Util::fmap(specs, [](auto& spec) { return FeatureSpec(spec, ""); }); - auto split_specs = find_unknown_ports_for_ci(paths, exclusions_set, paths_port_file, all_fspecs); - auto fspecs = Util::fmap(split_specs.unknown, [](auto& spec) { return FeatureSpec(spec, ""); }); + auto split_specs = + find_unknown_ports_for_ci(paths, exclusions_set, paths_port_file, all_fspecs, purge_tombstones); + auto fspecs = FullPackageSpec::to_feature_specs(split_specs.unknown); - auto action_plan = Dependencies::create_feature_install_plan(paths_port_file, fspecs, status_db); + for (auto&& fspec : fspecs) + pgraph.install(fspec); - for (auto&& action : action_plan) - { - if (auto p = action.install_action.get()) + auto action_plan = [&]() { + int iterations = 0; + do { - p->build_options = install_plan_options; - if (Util::Sets::contains(exclusions_set, p->spec.name())) + bool inconsistent = false; + auto action_plan = pgraph.serialize(); + + for (auto&& action : action_plan) { - p->plan_type = InstallPlanType::EXCLUDED; + if (auto p = action.install_action.get()) + { + p->build_options = install_plan_options; + if (Util::Sets::contains(exclusions_set, p->spec.name())) + { + p->plan_type = InstallPlanType::EXCLUDED; + } + + for (auto&& feature : split_specs.features[p->spec]) + if (p->feature_list.find(feature) == p->feature_list.end()) + { + pgraph.install({p->spec, feature}); + inconsistent = true; + } + } } - } - } + + if (!inconsistent) return action_plan; + Checks::check_exit(VCPKG_LINE_INFO, ++iterations < 100); + } while (true); + }(); if (is_dry_run) { @@ -251,7 +305,7 @@ namespace vcpkg::Commands::CI for (auto&& result : known_result) { xunit_doc += - Install::InstallSummary::xunit_result(result.first, Chrono::ElapsedTime{}, result.second); + Install::InstallSummary::xunit_result(result.first, Chrono::ElapsedTime {}, result.second); } } diff --git a/toolsrc/src/vcpkg/commands.cpp b/toolsrc/src/vcpkg/commands.cpp index 8b6ffb3d7..db265514f 100644 --- a/toolsrc/src/vcpkg/commands.cpp +++ b/toolsrc/src/vcpkg/commands.cpp @@ -1,5 +1,7 @@ #include "pch.h" +#include <vcpkg/base/hash.h> + #include <vcpkg/build.h> #include <vcpkg/commands.h> #include <vcpkg/export.h> @@ -43,7 +45,8 @@ namespace vcpkg::Commands {"portsdiff", &PortsDiff::perform_and_exit}, {"autocomplete", &Autocomplete::perform_and_exit}, {"hash", &Hash::perform_and_exit}, - // {"fetch", &Fetch::perform_and_exit}, + {"fetch", &Fetch::perform_and_exit}, + {"x-vsinstances", &X_VSInstances::perform_and_exit}, }; return t; } @@ -57,3 +60,47 @@ namespace vcpkg::Commands return t; } } + +namespace vcpkg::Commands::Fetch +{ + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be tool name\n%s", Help::create_example_string("fetch cmake")), + 1, + 1, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + const std::string tool = args.command_arguments[0]; + const fs::path tool_path = paths.get_tool_exe(tool); + System::println(tool_path.u8string()); + Checks::exit_success(VCPKG_LINE_INFO); + } +} + +namespace vcpkg::Commands::Hash +{ + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be a file path\n%s", + Help::create_example_string("hash boost_1_62_0.tar.bz2")), + 1, + 2, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + 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); + System::println(hash); + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.dependinfo.cpp b/toolsrc/src/vcpkg/commands.dependinfo.cpp index 1ca658216..5f72e965b 100644 --- a/toolsrc/src/vcpkg/commands.dependinfo.cpp +++ b/toolsrc/src/vcpkg/commands.dependinfo.cpp @@ -1,61 +1,166 @@ -#include "pch.h" - -#include <vcpkg/base/strings.h> -#include <vcpkg/base/system.h> -#include <vcpkg/base/util.h> -#include <vcpkg/commands.h> -#include <vcpkg/help.h> -#include <vcpkg/paragraphs.h> - -namespace vcpkg::Commands::DependInfo -{ - const CommandStructure COMMAND_STRUCTURE = { - Help::create_example_string(R"###(depend-info [pat])###"), - 0, - 1, - {}, - nullptr, - }; - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); - - std::vector<std::unique_ptr<SourceControlFile>> source_control_files = - Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); - - if (args.command_arguments.size() == 1) - { - const std::string filter = args.command_arguments.at(0); - - Util::erase_remove_if(source_control_files, - [&](const std::unique_ptr<SourceControlFile>& source_control_file) { - const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; - - if (Strings::case_insensitive_ascii_contains(source_paragraph.name, filter)) - { - return false; - } - - for (const Dependency& dependency : source_paragraph.depends) - { - if (Strings::case_insensitive_ascii_contains(dependency.name(), filter)) - { - return false; - } - } - - return true; - }); - } - - for (auto&& source_control_file : source_control_files) - { - const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; - const auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name(); }); - System::println("%s: %s", source_paragraph.name, s); - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} +#include "pch.h"
+
+#include <vcpkg/base/strings.h>
+#include <vcpkg/base/system.h>
+#include <vcpkg/base/util.h>
+#include <vcpkg/commands.h>
+#include <vcpkg/help.h>
+#include <vcpkg/paragraphs.h>
+
+namespace vcpkg::Commands::DependInfo
+{
+ constexpr StringLiteral OPTION_DOT = "--dot";
+ constexpr StringLiteral OPTION_DGML = "--dgml";
+
+ constexpr std::array<CommandSwitch, 2> DEPEND_SWITCHES = { {
+ { OPTION_DOT, "Creates graph on basis of dot" },
+ { OPTION_DGML, "Creates graph on basis of dgml" },
+ } };
+
+ const CommandStructure COMMAND_STRUCTURE = {
+ Help::create_example_string(R"###(depend-info [pat])###"),
+ 0,
+ 1,
+ { DEPEND_SWITCHES,{} },
+ nullptr,
+ };
+
+ std::string replace_dashes_with_underscore(const std::string& input)
+ {
+ std::string output = input;
+ std::replace(output.begin(), output.end(), '-', '_');
+ return output;
+ }
+
+ std::string create_dot_as_string(
+ const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files)
+ {
+ int empty_node_count = 0;
+
+ std::string s;
+ s.append("digraph G{ rankdir=LR; edge [minlen=3]; overlap=false;");
+
+ for (const auto& source_control_file : source_control_files)
+ {
+ const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
+ if (source_paragraph.depends.empty())
+ {
+ empty_node_count++;
+ continue;
+ }
+
+ const std::string name = replace_dashes_with_underscore(source_paragraph.name);
+ s.append(Strings::format("%s;", name));
+ for (const Dependency& d : source_paragraph.depends)
+ {
+ const std::string dependency_name = replace_dashes_with_underscore(d.name());
+ s.append(Strings::format("%s -> %s;", name, dependency_name));
+ }
+ }
+
+ 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)
+ {
+ const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
+ const std::string name = source_paragraph.name;
+ nodes.append(Strings::format("<Node Id=\"%s\" />", name));
+
+ // Iterate over dependencies.
+ for (const Dependency& d : source_paragraph.depends)
+ {
+ links.append(Strings::format("<Link Source=\"%s\" Target=\"%s\" />", name, d.name()));
+ }
+
+ // 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 Dependency& d : feature_paragraph->depends)
+ {
+ links.append(Strings::format("<Link Source=\"%s\" Target=\"%s\" />", name, d.name()));
+ }
+ }
+ }
+
+ s.append(Strings::format("<Nodes>%s</Nodes>", nodes));
+
+ s.append(Strings::format("<Links>%s</Links>", links));
+
+ s.append("</DirectedGraph>");
+ return s;
+ }
+
+ 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);
+ }
+ else if (Util::Sets::contains(switches, OPTION_DGML))
+ {
+ return create_dgml_as_string(source_control_files);
+ }
+ return "";
+ }
+
+ void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
+ {
+ const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE);
+
+ auto source_control_files = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports);
+
+ if (args.command_arguments.size() == 1)
+ {
+ const std::string filter = args.command_arguments.at(0);
+
+ Util::erase_remove_if(source_control_files,
+ [&](const std::unique_ptr<SourceControlFile>& source_control_file) {
+ const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
+
+ if (Strings::case_insensitive_ascii_contains(source_paragraph.name, filter))
+ {
+ return false;
+ }
+
+ for (const Dependency& dependency : source_paragraph.depends)
+ {
+ if (Strings::case_insensitive_ascii_contains(dependency.name(), filter))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ });
+ }
+
+ if (!options.switches.empty())
+ {
+ const std::string graph_as_string = create_graph_as_string(options.switches, source_control_files);
+ System::println(graph_as_string);
+ Checks::exit_success(VCPKG_LINE_INFO);
+ }
+
+ for (auto&& source_control_file : source_control_files)
+ {
+ const SourceParagraph& source_paragraph = *source_control_file->core_paragraph;
+ const auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name(); });
+ System::println("%s: %s", source_paragraph.name, s);
+ }
+
+ Checks::exit_success(VCPKG_LINE_INFO);
+ }
+}
diff --git a/toolsrc/src/vcpkg/commands.edit.cpp b/toolsrc/src/vcpkg/commands.edit.cpp index b6324565c..044ae1c47 100644 --- a/toolsrc/src/vcpkg/commands.edit.cpp +++ b/toolsrc/src/vcpkg/commands.edit.cpp @@ -64,21 +64,37 @@ namespace vcpkg::Commands::Edit { if (Util::Sets::contains(options.switches, OPTION_ALL)) { + const auto& fs = paths.get_filesystem(); + auto packages = fs.get_files_non_recursive(paths.packages); + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { const auto portpath = paths.ports / port_name; const auto portfile = portpath / "portfile.cmake"; const auto buildtrees_current_dir = paths.buildtrees / port_name; - return Strings::format(R"###("%s" "%s" "%s")###", + const auto pattern = port_name + "_"; + + std::string package_paths; + for (auto&& package : packages) + { + if (Strings::case_insensitive_ascii_starts_with(package.filename().u8string(), pattern)) + { + package_paths.append(Strings::format(" \"%s\"", package.u8string())); + } + } + + return Strings::format(R"###("%s" "%s" "%s"%s)###", portpath.u8string(), portfile.u8string(), - buildtrees_current_dir.u8string()); + buildtrees_current_dir.u8string(), + package_paths); }); } if (Util::Sets::contains(options.switches, OPTION_BUILDTREES)) { return Util::fmap(ports, [&](const std::string& port_name) -> std::string { - return (paths.buildtrees / port_name).u8string(); + const auto buildtrees_current_dir = paths.buildtrees / port_name; + return Strings::format(R"###("%s")###", buildtrees_current_dir.u8string()); }); } @@ -91,9 +107,6 @@ namespace vcpkg::Commands::Edit void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const fs::path VS_CODE_INSIDERS = fs::path{"Microsoft VS Code Insiders"} / "Code - Insiders.exe"; - static const fs::path VS_CODE = fs::path{"Microsoft VS Code"} / "Code.exe"; - auto& fs = paths.get_filesystem(); const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); @@ -113,6 +126,10 @@ namespace vcpkg::Commands::Edit candidate_paths.emplace_back(*editor_path); } +#ifdef _WIN32 + static const fs::path VS_CODE_INSIDERS = fs::path{"Microsoft VS Code Insiders"} / "Code - Insiders.exe"; + static const fs::path VS_CODE = fs::path{"Microsoft VS Code"} / "Code.exe"; + const auto& program_files = System::get_program_files_platform_bitness(); if (const fs::path* pf = program_files.get()) { @@ -127,8 +144,20 @@ namespace vcpkg::Commands::Edit candidate_paths.push_back(*pf / VS_CODE); } + const auto& app_data = System::get_environment_variable("APPDATA"); + if (const auto* ad = app_data.get()) + { + const fs::path default_base = fs::path{*ad}.parent_path() / "Local" / "Programs"; + candidate_paths.push_back(default_base / VS_CODE_INSIDERS); + candidate_paths.push_back(default_base / VS_CODE); + } + const std::vector<fs::path> from_registry = find_from_registry(); candidate_paths.insert(candidate_paths.end(), from_registry.cbegin(), from_registry.cend()); +#elif defined(__APPLE__) + candidate_paths.push_back(fs::path{"/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"}); + candidate_paths.push_back(fs::path{"/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"}); +#endif const auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { return fs.exists(p); }); if (it == candidate_paths.cend()) @@ -146,6 +175,16 @@ namespace vcpkg::Commands::Edit const std::vector<std::string> arguments = create_editor_arguments(paths, options, ports); const auto args_as_string = Strings::join(" ", arguments); const auto cmd_line = Strings::format(R"("%s" %s -n)", env_editor.u8string(), args_as_string); + + auto editor_exe = env_editor.filename().u8string(); + +#ifdef _WIN32 + if (editor_exe == "Code.exe" || editor_exe == "Code - Insiders.exe") + { + System::cmd_execute_no_wait(cmd_line + " <NUL"); + Checks::exit_success(VCPKG_LINE_INFO); + } +#endif Checks::exit_with_code(VCPKG_LINE_INFO, System::cmd_execute(cmd_line)); } } diff --git a/toolsrc/src/vcpkg/commands.env.cpp b/toolsrc/src/vcpkg/commands.env.cpp index d078baedb..ea00617d4 100644 --- a/toolsrc/src/vcpkg/commands.env.cpp +++ b/toolsrc/src/vcpkg/commands.env.cpp @@ -23,9 +23,9 @@ namespace vcpkg::Commands::Env }}; const CommandStructure COMMAND_STRUCTURE = { - Help::create_example_string("env --triplet x64-windows"), - 0, + Help::create_example_string("env <optional command> --triplet x64-windows"), 0, + 1, {SWITCHES, {}}, nullptr, }; @@ -64,11 +64,12 @@ namespace vcpkg::Commands::Env if (add_python) extra_env.emplace("PYTHONPATH", (paths.installed / triplet.to_string() / "python").u8string()); if (path_vars.size() > 0) extra_env.emplace("PATH", Strings::join(";", path_vars)); - if (env_cmd.empty()) - System::cmd_execute_clean("cmd", extra_env); - else - System::cmd_execute_clean(env_cmd + " && cmd", extra_env); + std::string env_cmd_prefix = env_cmd.empty() ? "" : Strings::format("%s && ", env_cmd); + std::string env_cmd_suffix = + args.command_arguments.empty() ? "cmd" : Strings::format("cmd /c %s", args.command_arguments.at(0)); + const std::string cmd = Strings::format("%s%s", env_cmd_prefix, env_cmd_suffix); + System::cmd_execute_clean(cmd, extra_env); Checks::exit_success(VCPKG_LINE_INFO); } } diff --git a/toolsrc/src/vcpkg/commands.exportifw.cpp b/toolsrc/src/vcpkg/commands.exportifw.cpp index ae106196a..62725a90a 100644 --- a/toolsrc/src/vcpkg/commands.exportifw.cpp +++ b/toolsrc/src/vcpkg/commands.exportifw.cpp @@ -13,7 +13,7 @@ namespace vcpkg::Export::IFW static std::string create_release_date() { - const tm date_time = System::get_current_date_time(); + 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 diff --git a/toolsrc/src/vcpkg/commands.fetch.cpp b/toolsrc/src/vcpkg/commands.fetch.cpp deleted file mode 100644 index 1e31e6bc4..000000000 --- a/toolsrc/src/vcpkg/commands.fetch.cpp +++ /dev/null @@ -1,714 +0,0 @@ -#include "pch.h" - -#include <vcpkg/base/checks.h> -#include <vcpkg/base/strings.h> -#include <vcpkg/base/system.h> -#include <vcpkg/base/util.h> -#include <vcpkg/commands.h> -#include <vcpkg/help.h> - -namespace vcpkg::Commands::Fetch -{ - static constexpr CStringView V_120 = "v120"; - static constexpr CStringView V_140 = "v140"; - static constexpr CStringView V_141 = "v141"; - - struct ToolData - { - std::array<int, 3> version; - fs::path exe_path; - std::string url; - fs::path download_path; - fs::path tool_dir_path; - std::string sha512; - }; - - static Optional<std::array<int, 3>> parse_version_string(const std::string& version_as_string) - { - static const std::regex RE(R"###((\d+)\.(\d+)\.(\d+))###"); - - std::match_results<std::string::const_iterator> match; - const auto found = std::regex_search(version_as_string, match, RE); - if (!found) - { - return {}; - } - - const int d1 = atoi(match[1].str().c_str()); - const int d2 = atoi(match[2].str().c_str()); - const int d3 = atoi(match[3].str().c_str()); - const std::array<int, 3> result = {d1, d2, d3}; - return result; - } - - static Optional<std::string> extract_string_between_delimiters(const std::string& input, - const std::string& left_delim, - const std::string& right_delim, - const size_t& starting_offset = 0) - { - const size_t from = input.find(left_delim, starting_offset); - if (from == std::string::npos) return nullopt; - - const size_t substring_start = from + left_delim.length(); - - const size_t to = input.find(right_delim, substring_start); - if (from == std::string::npos) return nullopt; - - return input.substr(substring_start, to - substring_start); - } - - static ToolData parse_tool_data_from_xml(const VcpkgPaths& paths, const std::string& tool) - { -#if defined(_WIN32) - static constexpr StringLiteral OS_STRING = "windows"; -#elif defined(__APPLE__) - static constexpr StringLiteral OS_STRING = "osx"; -#elif defined(__linux__) - static constexpr StringLiteral OS_STRING = "linux"; -#else - return ToolData{}; -#endif - -#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) - static const std::string XML_VERSION = "2"; - static const fs::path XML_PATH = paths.scripts / "vcpkgTools.xml"; - - const auto get_string_inside_tags = - [](const std::string& input, const std::string& left_delim, const std::string& right_delim) -> std::string { - Optional<std::string> result = extract_string_between_delimiters(input, left_delim, right_delim); - Checks::check_exit(VCPKG_LINE_INFO, - result.has_value(), - "Could not find tag <%s>.*<%s> in %s", - left_delim, - right_delim, - XML_PATH.generic_string()); - - return *result.get(); - }; - - static const std::regex XML_VERSION_REGEX{R"###(<tools[\s]+version="([^"]+)">)###"}; - static const std::string XML = paths.get_filesystem().read_contents(XML_PATH).value_or_exit(VCPKG_LINE_INFO); - std::smatch match_xml_version; - const bool has_xml_version = std::regex_search(XML.cbegin(), XML.cend(), match_xml_version, XML_VERSION_REGEX); - Checks::check_exit(VCPKG_LINE_INFO, - has_xml_version, - R"(Could not find <tools version="%s"> in %s)", - XML_VERSION, - XML_PATH.generic_string()); - Checks::check_exit(VCPKG_LINE_INFO, - XML_VERSION == match_xml_version[1], - "Expected %s version: [%s], but was [%s]. Please re-run bootstrap-vcpkg.", - XML_PATH.generic_string(), - XML_VERSION, - match_xml_version[1]); - - const std::regex tool_regex{Strings::format(R"###(<tool[\s]+name="%s"[\s]+os="%s">)###", tool, OS_STRING)}; - std::smatch match_tool_entry; - const bool has_tool_entry = std::regex_search(XML.cbegin(), XML.cend(), match_tool_entry, tool_regex); - Checks::check_exit(VCPKG_LINE_INFO, - has_tool_entry, - "Could not find entry for tool [%s] in %s", - tool, - XML_PATH.generic_string()); - - const std::string tool_data = get_string_inside_tags(XML, match_tool_entry[0], R"(</tool>)"); - - const std::string version_as_string = get_string_inside_tags(tool_data, "<version>", R"(</version>)"); - const std::string exe_relative_path = - get_string_inside_tags(tool_data, "<exeRelativePath>", R"(</exeRelativePath>)"); - const std::string url = get_string_inside_tags(tool_data, "<url>", R"(</url>)"); - const std::string sha512 = get_string_inside_tags(tool_data, "<sha512>", R"(</sha512>)"); - auto archive_name = extract_string_between_delimiters(tool_data, "<archiveName>", R"(</archiveName>)"); - - const Optional<std::array<int, 3>> version = parse_version_string(version_as_string); - Checks::check_exit(VCPKG_LINE_INFO, - version.has_value(), - "Could not parse version for tool %s. Version string was: %s", - tool, - version_as_string); - - const std::string tool_dir_name = Strings::format("%s-%s-%s", tool, version_as_string, OS_STRING); - const fs::path tool_dir_path = paths.downloads / "tools" / tool_dir_name; - const fs::path exe_path = tool_dir_path / exe_relative_path; - - return ToolData{*version.get(), - exe_path, - url, - paths.downloads / archive_name.value_or(exe_relative_path), - tool_dir_path, - sha512}; -#endif - } - - static bool exists_and_has_equal_or_greater_version(const std::string& version_cmd, - const std::array<int, 3>& expected_version) - { - const auto rc = System::cmd_execute_and_capture_output(Strings::format(R"(%s)", version_cmd)); - if (rc.exit_code != 0) - { - return false; - } - - const Optional<std::array<int, 3>> v = parse_version_string(rc.output); - if (!v.has_value()) - { - return false; - } - - const std::array<int, 3> actual_version = *v.get(); - return (actual_version[0] > expected_version[0] || - (actual_version[0] == expected_version[0] && actual_version[1] > expected_version[1]) || - (actual_version[0] == expected_version[0] && actual_version[1] == expected_version[1] && - actual_version[2] >= expected_version[2])); - } - - static Optional<fs::path> find_if_has_equal_or_greater_version(const std::vector<fs::path>& candidate_paths, - const std::string& version_check_arguments, - const std::array<int, 3>& expected_version) - { - auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { - const std::string cmd = Strings::format(R"("%s" %s)", p.u8string(), version_check_arguments); - return exists_and_has_equal_or_greater_version(cmd, expected_version); - }); - - if (it != candidate_paths.cend()) - { - return std::move(*it); - } - - return nullopt; - } - - static std::vector<std::string> keep_data_lines(const std::string& data_blob) - { - static const std::regex DATA_LINE_REGEX(R"(<sol>::(.+?)(?=::<eol>))"); - - std::vector<std::string> data_lines; - - const std::sregex_iterator it(data_blob.cbegin(), data_blob.cend(), DATA_LINE_REGEX); - const std::sregex_iterator end; - for (std::sregex_iterator i = it; i != end; ++i) - { - const std::smatch match = *i; - data_lines.push_back(match[1].str()); - } - - return data_lines; - } - -#if !defined(_WIN32) - static void extract_archive(const VcpkgPaths& paths, const fs::path& archive, const fs::path& to_path) - { - Files::Filesystem& fs = paths.get_filesystem(); - const fs::path to_path_partial = to_path.u8string() + ".partial"; - - std::error_code ec; - fs.remove_all(to_path_partial, ec); - fs.create_directories(to_path_partial, ec); - - const auto ext = archive.extension(); - if (ext == ".gz" && ext.extension() != ".tar") - { - const auto code = System::cmd_execute( - Strings::format(R"(cd '%s' && tar xzf '%s')", to_path_partial.u8string(), archive.u8string())); - Checks::check_exit(VCPKG_LINE_INFO, code == 0, "tar failed while extracting %s", archive.u8string()); - } - else if (ext == ".zip") - { - const auto code = System::cmd_execute( - Strings::format(R"(cd '%s' && unzip -qqo '%s')", to_path_partial.u8string(), archive.u8string())); - Checks::check_exit(VCPKG_LINE_INFO, code == 0, "unzip failed while extracting %s", archive.u8string()); - } - else - { - Checks::exit_with_message(VCPKG_LINE_INFO, "Unexpected archive extension: %s", ext.u8string()); - } - - fs.rename(to_path_partial, to_path); - } - - static void verify_hash(const VcpkgPaths& paths, - const std::string& url, - const fs::path& path, - const std::string& sha512) - { - const std::string actual_hash = Hash::get_file_hash(paths, path, "SHA512"); - Checks::check_exit(VCPKG_LINE_INFO, - sha512 == actual_hash, - "File does not have the expected hash:\n" - " url : [ %s ]\n" - " File path : [ %s ]\n" - " Expected hash : [ %s ]\n" - " Actual hash : [ %s ]\n", - url, - path.u8string(), - sha512, - actual_hash); - } - - static void download_file(const VcpkgPaths& paths, - const std::string& url, - const fs::path& download_path, - const std::string& sha512) - { - Files::Filesystem& fs = paths.get_filesystem(); - const std::string download_path_part = download_path.u8string() + ".part"; - std::error_code ec; - fs.remove(download_path_part, ec); - const auto code = System::cmd_execute(Strings::format( - R"(curl -L '%s' --create-dirs --output '%s')", url, download_path_part)); - Checks::check_exit(VCPKG_LINE_INFO, code == 0, "Could not download %s", url); - - verify_hash(paths, url, download_path_part, sha512); - fs.rename(download_path_part, download_path); - } - -#endif - static fs::path fetch_tool(const VcpkgPaths& paths, const std::string& tool_name, const ToolData& tool_data) - { - const std::array<int, 3>& version = tool_data.version; - - const std::string version_as_string = Strings::format("%d.%d.%d", version[0], version[1], version[2]); - System::println("A suitable version of %s was not found (required v%s). Downloading portable %s v%s...", - tool_name, - version_as_string, - tool_name, - version_as_string); -#if defined(_WIN32) - const fs::path script = paths.scripts / "fetchtool.ps1"; - const std::string title = Strings::format( - "Fetching %s version %s (No sufficient installed version was found)", tool_name, version_as_string); - const System::PowershellParameter tool_param("tool", tool_name); - const std::string output = System::powershell_execute_and_capture_output(title, script, {tool_param}); - - const std::vector<std::string> tool_path = keep_data_lines(output); - Checks::check_exit(VCPKG_LINE_INFO, tool_path.size() == 1, "Expected tool path, but got %s", output); - - const fs::path actual_downloaded_path = Strings::trim(std::string{tool_path.at(0)}); - const fs::path& expected_downloaded_path = tool_data.exe_path; - std::error_code ec; - const auto eq = fs::stdfs::equivalent(expected_downloaded_path, actual_downloaded_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - eq && !ec, - "Expected tool downloaded path to be %s, but was %s", - expected_downloaded_path.u8string(), - actual_downloaded_path.u8string()); - return actual_downloaded_path; -#else - const auto& fs = paths.get_filesystem(); - if (!fs.exists(tool_data.download_path)) - { - System::println("Downloading %s...", tool_name); - download_file(paths, tool_data.url, tool_data.download_path, tool_data.sha512); - System::println("Downloading %s... done.", tool_name); - } - else - { - verify_hash(paths, tool_data.url, tool_data.download_path, tool_data.sha512); - } - - System::println("Extracting %s...", tool_name); - extract_archive(paths, tool_data.download_path, tool_data.tool_dir_path); - System::println("Extracting %s... done.", tool_name); - - Checks::check_exit(VCPKG_LINE_INFO, - fs.exists(tool_data.exe_path), - "Expected %s to exist after extracting", - tool_data.exe_path); - - return tool_data.exe_path; -#endif - } - - static fs::path get_cmake_path(const VcpkgPaths& paths) - { - std::vector<fs::path> candidate_paths; -#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "cmake"); - candidate_paths.push_back(TOOL_DATA.exe_path); -#else - static const ToolData TOOL_DATA = ToolData{{3, 5, 1}, ""}; -#endif - static const std::string VERSION_CHECK_ARGUMENTS = "--version"; - - const std::vector<fs::path> from_path = Files::find_from_PATH("cmake"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - - const auto& program_files = System::get_program_files_platform_bitness(); - if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); - const auto& program_files_32_bit = System::get_program_files_32_bit(); - if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths, "cmake", TOOL_DATA); - } - - static fs::path get_7za_path(const VcpkgPaths& paths) - { -#if defined(_WIN32) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "7zip"); - if (!paths.get_filesystem().exists(TOOL_DATA.exe_path)) - { - return fetch_tool(paths, "7zip", TOOL_DATA); - } - return TOOL_DATA.exe_path; -#else - Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot download 7zip for non-Windows platforms."); -#endif - } - - static fs::path get_ninja_path(const VcpkgPaths& paths) - { - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "ninja"); - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(TOOL_DATA.exe_path); - const std::vector<fs::path> from_path = Files::find_from_PATH("ninja"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - - auto path = find_if_has_equal_or_greater_version(candidate_paths, "--version", TOOL_DATA.version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths, "ninja", TOOL_DATA); - } - - static fs::path get_nuget_path(const VcpkgPaths& paths) - { - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "nuget"); - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(TOOL_DATA.exe_path); - const std::vector<fs::path> from_path = Files::find_from_PATH("nuget"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - - auto path = find_if_has_equal_or_greater_version(candidate_paths, "", TOOL_DATA.version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths, "nuget", TOOL_DATA); - } - - static fs::path get_git_path(const VcpkgPaths& paths) - { -#if defined(_WIN32) - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "git"); -#else - static const ToolData TOOL_DATA = ToolData{{2, 7, 4}, ""}; -#endif - static const std::string VERSION_CHECK_ARGUMENTS = "--version"; - - std::vector<fs::path> candidate_paths; -#if defined(_WIN32) - candidate_paths.push_back(TOOL_DATA.exe_path); -#endif - const std::vector<fs::path> from_path = Files::find_from_PATH("git"); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - - const auto& program_files = System::get_program_files_platform_bitness(); - if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); - const auto& program_files_32_bit = System::get_program_files_32_bit(); - if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths, "git", TOOL_DATA); - } - - static fs::path get_ifw_installerbase_path(const VcpkgPaths& paths) - { - static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "installerbase"); - - static const std::string VERSION_CHECK_ARGUMENTS = "--framework-version"; - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(TOOL_DATA.exe_path); - // TODO: Uncomment later - // const std::vector<fs::path> from_path = Files::find_from_PATH("installerbase"); - // candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / - // "Tools" / "QtInstallerFramework" / "3.1" / "bin" / "installerbase.exe"); - // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / - // "QtIFW-3.1.0" / "bin" / "installerbase.exe"); - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); - if (const auto p = path.get()) - { - return *p; - } - - return fetch_tool(paths, "installerbase", TOOL_DATA); - } - - struct VisualStudioInstance - { - fs::path root_path; - std::string version; - std::string release_type; - std::string preference_weight; // Mostly unused, just for verification that order is as intended - - std::string major_version() const { return version.substr(0, 2); } - }; - - static std::vector<VisualStudioInstance> get_visual_studio_instances(const VcpkgPaths& paths) - { - const fs::path script = paths.scripts / "findVisualStudioInstallationInstances.ps1"; - const std::string output = - System::powershell_execute_and_capture_output("Detecting Visual Studio instances", script); - - const std::vector<std::string> instances_as_strings = keep_data_lines(output); - Checks::check_exit(VCPKG_LINE_INFO, - !instances_as_strings.empty(), - "Could not detect any Visual Studio instances.\n" - "Powershell script:\n" - " %s\n" - "returned:\n" - "%s", - script.generic_string(), - output); - - std::vector<VisualStudioInstance> instances; - for (const std::string& instance_as_string : instances_as_strings) - { - const std::vector<std::string> split = Strings::split(instance_as_string, "::"); - Checks::check_exit(VCPKG_LINE_INFO, - split.size() == 4, - "Invalid Visual Studio instance format.\n" - "Expected: PreferenceWeight::ReleaseType::Version::PathToVisualStudio\n" - "Actual : %s\n", - instance_as_string); - instances.push_back({split.at(3), split.at(2), split.at(1), split.at(0)}); - } - - return instances; - } - - std::vector<Toolset> find_toolset_instances(const VcpkgPaths& paths) - { - using CPU = System::CPUArchitecture; - - const auto& fs = paths.get_filesystem(); - - // Note: this will contain a mix of vcvarsall.bat locations and dumpbin.exe locations. - std::vector<fs::path> paths_examined; - - std::vector<Toolset> found_toolsets; - std::vector<Toolset> excluded_toolsets; - - const std::vector<VisualStudioInstance> vs_instances = get_visual_studio_instances(paths); - const bool v140_is_available = Util::find_if(vs_instances, [&](const VisualStudioInstance& vs_instance) { - return vs_instance.major_version() == "14"; - }) != vs_instances.cend(); - - for (const VisualStudioInstance& vs_instance : vs_instances) - { - const std::string major_version = vs_instance.major_version(); - if (major_version == "15") - { - const fs::path vc_dir = vs_instance.root_path / "VC"; - - // Skip any instances that do not have vcvarsall. - const fs::path vcvarsall_dir = vc_dir / "Auxiliary" / "Build"; - const fs::path vcvarsall_bat = vcvarsall_dir / "vcvarsall.bat"; - paths_examined.push_back(vcvarsall_bat); - if (!fs.exists(vcvarsall_bat)) continue; - - // Get all supported architectures - std::vector<ToolsetArchOption> supported_architectures; - if (fs.exists(vcvarsall_dir / "vcvars32.bat")) - supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); - if (fs.exists(vcvarsall_dir / "vcvars64.bat")) - supported_architectures.push_back({"amd64", CPU::X64, CPU::X64}); - if (fs.exists(vcvarsall_dir / "vcvarsx86_amd64.bat")) - supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); - if (fs.exists(vcvarsall_dir / "vcvarsx86_arm.bat")) - supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); - if (fs.exists(vcvarsall_dir / "vcvarsx86_arm64.bat")) - supported_architectures.push_back({"x86_arm64", CPU::X86, CPU::ARM64}); - if (fs.exists(vcvarsall_dir / "vcvarsamd64_x86.bat")) - supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); - if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm.bat")) - supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); - if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm64.bat")) - supported_architectures.push_back({"amd64_arm64", CPU::X64, CPU::ARM64}); - - // Locate the "best" MSVC toolchain version - const fs::path msvc_path = vc_dir / "Tools" / "MSVC"; - std::vector<fs::path> msvc_subdirectories = fs.get_files_non_recursive(msvc_path); - Util::unstable_keep_if(msvc_subdirectories, - [&fs](const fs::path& path) { return fs.is_directory(path); }); - - // Sort them so that latest comes first - std::sort( - msvc_subdirectories.begin(), - msvc_subdirectories.end(), - [](const fs::path& left, const fs::path& right) { return left.filename() > right.filename(); }); - - for (const fs::path& subdir : msvc_subdirectories) - { - const fs::path dumpbin_path = subdir / "bin" / "HostX86" / "x86" / "dumpbin.exe"; - paths_examined.push_back(dumpbin_path); - if (fs.exists(dumpbin_path)) - { - const Toolset v141toolset = Toolset{ - vs_instance.root_path, dumpbin_path, vcvarsall_bat, {}, V_141, supported_architectures}; - - auto english_language_pack = dumpbin_path.parent_path() / "1033"; - - if (!fs.exists(english_language_pack)) - { - excluded_toolsets.push_back(v141toolset); - break; - } - - found_toolsets.push_back(v141toolset); - - if (v140_is_available) - { - const Toolset v140toolset = Toolset{vs_instance.root_path, - dumpbin_path, - vcvarsall_bat, - {"-vcvars_ver=14.0"}, - V_140, - supported_architectures}; - found_toolsets.push_back(v140toolset); - } - - break; - } - } - - continue; - } - - if (major_version == "14" || major_version == "12") - { - const fs::path vcvarsall_bat = vs_instance.root_path / "VC" / "vcvarsall.bat"; - - paths_examined.push_back(vcvarsall_bat); - if (fs.exists(vcvarsall_bat)) - { - const fs::path vs_dumpbin_exe = vs_instance.root_path / "VC" / "bin" / "dumpbin.exe"; - paths_examined.push_back(vs_dumpbin_exe); - - const fs::path vs_bin_dir = vcvarsall_bat.parent_path() / "bin"; - std::vector<ToolsetArchOption> supported_architectures; - if (fs.exists(vs_bin_dir / "vcvars32.bat")) - supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); - if (fs.exists(vs_bin_dir / "amd64\\vcvars64.bat")) - supported_architectures.push_back({"x64", CPU::X64, CPU::X64}); - if (fs.exists(vs_bin_dir / "x86_amd64\\vcvarsx86_amd64.bat")) - supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); - if (fs.exists(vs_bin_dir / "x86_arm\\vcvarsx86_arm.bat")) - supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); - if (fs.exists(vs_bin_dir / "amd64_x86\\vcvarsamd64_x86.bat")) - supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); - if (fs.exists(vs_bin_dir / "amd64_arm\\vcvarsamd64_arm.bat")) - supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); - - if (fs.exists(vs_dumpbin_exe)) - { - const Toolset toolset = {vs_instance.root_path, - vs_dumpbin_exe, - vcvarsall_bat, - {}, - major_version == "14" ? V_140 : V_120, - supported_architectures}; - - auto english_language_pack = vs_dumpbin_exe.parent_path() / "1033"; - - if (!fs.exists(english_language_pack)) - { - excluded_toolsets.push_back(toolset); - break; - } - - found_toolsets.push_back(toolset); - } - } - } - } - - if (!excluded_toolsets.empty()) - { - System::println( - System::Color::warning, - "Warning: The following VS instances are excluded because the English language pack is unavailable."); - for (const Toolset& toolset : excluded_toolsets) - { - System::println(" %s", toolset.visual_studio_root_path.u8string()); - } - System::println(System::Color::warning, "Please install the English language pack."); - } - - if (found_toolsets.empty()) - { - System::println(System::Color::error, "Could not locate a complete toolset."); - System::println("The following paths were examined:"); - for (const fs::path& path : paths_examined) - { - System::println(" %s", path.u8string()); - } - Checks::exit_fail(VCPKG_LINE_INFO); - } - - return found_toolsets; - } - - fs::path get_tool_path(const VcpkgPaths& paths, const std::string& tool) - { - // First deal with specially handled tools. - // For these we may look in locations like Program Files, the PATH etc as well as the auto-downloaded location. - if (tool == Tools::SEVEN_ZIP) return get_7za_path(paths); - if (tool == Tools::CMAKE) return get_cmake_path(paths); - if (tool == Tools::GIT) return get_git_path(paths); - if (tool == Tools::NINJA) return get_ninja_path(paths); - if (tool == Tools::NUGET) return get_nuget_path(paths); - if (tool == Tools::IFW_INSTALLER_BASE) return get_ifw_installerbase_path(paths); - if (tool == Tools::IFW_BINARYCREATOR) - return get_ifw_installerbase_path(paths).parent_path() / "binarycreator.exe"; - if (tool == Tools::IFW_REPOGEN) return get_ifw_installerbase_path(paths).parent_path() / "repogen.exe"; - - // For other tools, we simply always auto-download them. - const ToolData tool_data = parse_tool_data_from_xml(paths, tool); - if (paths.get_filesystem().exists(tool_data.exe_path)) - { - return tool_data.exe_path; - } - return fetch_tool(paths, tool, tool_data); - } - - const CommandStructure COMMAND_STRUCTURE = { - Strings::format("The argument should be tool name\n%s", Help::create_example_string("fetch cmake")), - 1, - 1, - {}, - nullptr, - }; - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); - - const std::string tool = args.command_arguments[0]; - const fs::path tool_path = get_tool_path(paths, tool); - System::println(tool_path.u8string()); - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/vcpkg/commands.integrate.cpp b/toolsrc/src/vcpkg/commands.integrate.cpp index 8897ea138..82172e363 100644 --- a/toolsrc/src/vcpkg/commands.integrate.cpp +++ b/toolsrc/src/vcpkg/commands.integrate.cpp @@ -5,6 +5,7 @@ #include <vcpkg/base/system.h> #include <vcpkg/base/util.h> #include <vcpkg/commands.h> +#include <vcpkg/metrics.h> #include <vcpkg/userconfig.h> namespace vcpkg::Commands::Integrate @@ -369,6 +370,43 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console #endif #if defined(_WIN32) + static void integrate_powershell(const VcpkgPaths& paths) + { + 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 std::string cmd = Strings::format( + R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' %s}")", script_path.u8string(), ""); + const int rc = System::cmd_execute(cmd); + + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + if (rc) + { + System::println(System::Color::error, + "%s\n" + "Could not run:\n" + " '%s'", + TITLE, + script_path.generic_string()); + + { + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->track_property("error", "powershell script failed"); + locked_metrics->track_property("title", TITLE); + } + } + + Checks::exit_with_code(VCPKG_LINE_INFO, rc); + } +#endif + +#if defined(_WIN32) const char* const INTEGRATE_COMMAND_HELPSTRING = " vcpkg integrate install Make installed packages available user-wide. Requires admin privileges on " "first use\n" @@ -423,9 +461,7 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console } if (args.command_arguments[0] == Subcommand::POWERSHELL) { - System::powershell_execute("PowerShell Tab-Completion", - paths.scripts / "addPoshVcpkgToPowershellProfile.ps1"); - Checks::exit_success(VCPKG_LINE_INFO); + return integrate_powershell(paths); } #endif diff --git a/toolsrc/src/vcpkg/commands.list.cpp b/toolsrc/src/vcpkg/commands.list.cpp index 1bfbc4247..cadc06ad3 100644 --- a/toolsrc/src/vcpkg/commands.list.cpp +++ b/toolsrc/src/vcpkg/commands.list.cpp @@ -76,7 +76,7 @@ namespace vcpkg::Commands::List for (const StatusParagraph* status_paragraph : installed_packages) { const std::string displayname = status_paragraph->package.displayname(); - if (Strings::case_insensitive_ascii_find(displayname, args.command_arguments[0]) == displayname.end()) + if (!Strings::case_insensitive_ascii_contains(displayname, args.command_arguments[0])) { continue; } diff --git a/toolsrc/src/vcpkg/commands.search.cpp b/toolsrc/src/vcpkg/commands.search.cpp index 33a642e40..263c86698 100644 --- a/toolsrc/src/vcpkg/commands.search.cpp +++ b/toolsrc/src/vcpkg/commands.search.cpp @@ -10,46 +10,9 @@ namespace vcpkg::Commands::Search { - static constexpr StringLiteral OPTION_GRAPH = "--graph"; // TODO: This should find a better home, eventually static constexpr StringLiteral OPTION_FULLDESC = "--x-full-desc"; // TODO: This should find a better home, eventually - - static std::string replace_dashes_with_underscore(const std::string& input) - { - std::string output = input; - std::replace(output.begin(), output.end(), '-', '_'); - return output; - } - - static std::string create_graph_as_string( - const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files) - { - int empty_node_count = 0; - - std::string s; - s.append("digraph G{ rankdir=LR; edge [minlen=3]; overlap=false;"); - - for (const auto& source_control_file : source_control_files) - { - const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; - if (source_paragraph.depends.empty()) - { - empty_node_count++; - continue; - } - - const std::string name = replace_dashes_with_underscore(source_paragraph.name); - s.append(Strings::format("%s;", name)); - for (const Dependency& d : source_paragraph.depends) - { - const std::string dependency_name = replace_dashes_with_underscore(d.name()); - s.append(Strings::format("%s -> %s;", name, dependency_name)); - } - } - - s.append(Strings::format("empty [label=\"%d singletons...\"]; }", empty_node_count)); - return s; - } + static void do_print(const SourceParagraph& source_paragraph, bool full_desc) { if (full_desc) @@ -80,8 +43,7 @@ namespace vcpkg::Commands::Search } } - static constexpr std::array<CommandSwitch, 2> SEARCH_SWITCHES = {{ - {OPTION_GRAPH, "Open editor into the port-specific buildtree subfolder"}, + static constexpr std::array<CommandSwitch, 1> SEARCH_SWITCHES = {{ {OPTION_FULLDESC, "Do not truncate long text"}, }}; @@ -102,13 +64,6 @@ namespace vcpkg::Commands::Search auto source_paragraphs = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); - if (Util::Sets::contains(options.switches, OPTION_GRAPH)) - { - const std::string graph_as_string = create_graph_as_string(source_paragraphs); - System::println(graph_as_string); - Checks::exit_success(VCPKG_LINE_INFO); - } - if (args.command_arguments.empty()) { for (const auto& source_control_file : source_paragraphs) diff --git a/toolsrc/src/vcpkg/commands.upgrade.cpp b/toolsrc/src/vcpkg/commands.upgrade.cpp index a902ddeaf..0e58b012d 100644 --- a/toolsrc/src/vcpkg/commands.upgrade.cpp +++ b/toolsrc/src/vcpkg/commands.upgrade.cpp @@ -1,8 +1,8 @@ #include "pch.h" -#include <vcpkg/base/util.h> #include <vcpkg/commands.h> #include <vcpkg/dependencies.h> +#include <vcpkg/globalstate.h> #include <vcpkg/help.h> #include <vcpkg/input.h> #include <vcpkg/install.h> @@ -10,6 +10,8 @@ #include <vcpkg/update.h> #include <vcpkg/vcpkglib.h> +#include <vcpkg/base/util.h> + namespace vcpkg::Commands::Upgrade { using Install::KeepGoing; @@ -141,11 +143,15 @@ namespace vcpkg::Commands::Upgrade Checks::check_exit(VCPKG_LINE_INFO, !plan.empty()); - const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, - Build::AllowDownloads::YES, - Build::CleanBuildtrees::NO, - Build::CleanPackages::NO, - Build::DownloadTool::BUILT_IN}; + const Build::BuildPackageOptions install_plan_options = { + Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN, + GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, + Build::FailOnTombstone::NO, + }; // Set build settings for all install actions for (auto&& action : plan) diff --git a/toolsrc/src/vcpkg/commands.xvsinstances.cpp b/toolsrc/src/vcpkg/commands.xvsinstances.cpp new file mode 100644 index 000000000..d748b6b2f --- /dev/null +++ b/toolsrc/src/vcpkg/commands.xvsinstances.cpp @@ -0,0 +1,33 @@ +#include "pch.h"
+
+#include <vcpkg/commands.h>
+#include <vcpkg/help.h>
+#include <vcpkg/visualstudio.h>
+
+namespace vcpkg::Commands::X_VSInstances
+{
+ const CommandStructure COMMAND_STRUCTURE = {
+ Help::create_example_string("x-vsinstances"),
+ 0,
+ 0,
+ {{}, {}},
+ nullptr,
+ };
+
+ void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths)
+ {
+#if defined(_WIN32)
+ const ParsedArguments parsed_args = args.parse_arguments(COMMAND_STRUCTURE);
+
+ const auto instances = vcpkg::VisualStudio::get_visual_studio_instances(paths);
+ for (const std::string& instance : instances)
+ {
+ System::println(instance);
+ }
+
+ Checks::exit_success(VCPKG_LINE_INFO);
+#else
+ 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 8fb35b0da..60c43e4a8 100644 --- a/toolsrc/src/vcpkg/dependencies.cpp +++ b/toolsrc/src/vcpkg/dependencies.cpp @@ -488,26 +488,23 @@ namespace vcpkg::Dependencies if (plus) return MarkPlusResult::SUCCESS; plus = true; + auto p_source = cluster.source.get(); + Checks::check_exit(VCPKG_LINE_INFO, + p_source != nullptr, + "Error: Cannot find definition for package `%s`.", + cluster.spec.name()); + if (feature.empty()) { // Add default features for this package. This is an exact reference, so ignore prevent_default_features. - if (auto p_source = cluster.source.get()) + for (auto&& default_feature : p_source->scf->core_paragraph.get()->default_features) { - for (auto&& default_feature : p_source->scf->core_paragraph.get()->default_features) + auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) { - auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); - if (res != MarkPlusResult::SUCCESS) - { - return res; - } + return res; } } - else - { - Checks::exit_with_message(VCPKG_LINE_INFO, - "Error: Unable to install default features because can't find CONTROL for %s", - cluster.spec); - } // "core" is always required. return mark_plus("core", cluster, graph, graph_plan, prevent_default_features); @@ -515,28 +512,20 @@ namespace vcpkg::Dependencies if (feature == "*") { - if (auto p_source = cluster.source.get()) + for (auto&& fpgh : p_source->scf->feature_paragraphs) { - for (auto&& fpgh : p_source->scf->feature_paragraphs) - { - auto res = mark_plus(fpgh->name, cluster, graph, graph_plan, prevent_default_features); + auto res = mark_plus(fpgh->name, cluster, graph, graph_plan, prevent_default_features); - Checks::check_exit(VCPKG_LINE_INFO, - res == MarkPlusResult::SUCCESS, - "Error: Unable to locate feature %s in %s", - fpgh->name, - cluster.spec); - } + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: Internal error while installing feature %s in %s", + fpgh->name, + cluster.spec); + } - auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); + auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); - Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); - } - else - { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: Unable to handle '*' because can't find CONTROL for %s", cluster.spec); - } + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); return MarkPlusResult::SUCCESS; } @@ -643,7 +632,7 @@ namespace vcpkg::Dependencies } /// <summary>Figure out which actions are required to install features specifications in `specs`.</summary> - /// <param name="map">Map of all source files in the current environment.</param> + /// <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, @@ -666,7 +655,11 @@ namespace vcpkg::Dependencies auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); - Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: `%s` is not a feature of package `%s`", + spec.feature(), + spec.name()); m_graph_plan->install_graph.add_vertex(ClusterPtr{&spec_cluster}); } diff --git a/toolsrc/src/vcpkg/export.cpp b/toolsrc/src/vcpkg/export.cpp index 152252018..eec9a39f2 100644 --- a/toolsrc/src/vcpkg/export.cpp +++ b/toolsrc/src/vcpkg/export.cpp @@ -1,8 +1,5 @@ #include "pch.h" -#include <vcpkg/base/stringliteral.h> -#include <vcpkg/base/system.h> -#include <vcpkg/base/util.h> #include <vcpkg/commands.h> #include <vcpkg/dependencies.h> #include <vcpkg/export.h> @@ -13,6 +10,10 @@ #include <vcpkg/paragraphs.h> #include <vcpkg/vcpkglib.h> +#include <vcpkg/base/stringliteral.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> + namespace vcpkg::Export { using Dependencies::ExportPlanAction; @@ -68,11 +69,15 @@ namespace vcpkg::Export { static constexpr std::array<ExportPlanType, 2> ORDER = {ExportPlanType::ALREADY_BUILT, ExportPlanType::NOT_BUILT}; - static constexpr Build::BuildPackageOptions BUILD_OPTIONS = {Build::UseHeadVersion::NO, - Build::AllowDownloads::YES, - Build::CleanBuildtrees::NO, - Build::CleanPackages::NO, - Build::DownloadTool::BUILT_IN}; + static constexpr Build::BuildPackageOptions BUILD_OPTIONS = { + Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN, + Build::BinaryCaching::NO, + Build::FailOnTombstone::NO, + }; for (const ExportPlanType plan_type : ORDER) { @@ -103,7 +108,7 @@ namespace vcpkg::Export static std::string create_export_id() { - const tm date_time = System::get_current_date_time(); + const tm date_time = Chrono::get_current_date_time_local(); // Format is: YYYYmmdd-HHMMSS // 15 characters + 1 null terminating character will be written for a total of 16 chars @@ -150,7 +155,7 @@ namespace vcpkg::Export const int exit_code = System::cmd_execute_clean(cmd_line); Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: NuGet package creation failed"); - const fs::path output_path = output_dir / (nuget_id + ".nupkg"); + const fs::path output_path = output_dir / (nuget_id + "." + nuget_version + ".nupkg"); return output_path; } @@ -222,14 +227,10 @@ namespace vcpkg::Export { const std::vector<fs::path> integration_files_relative_to_root = { {".vcpkg-root"}, - {fs::path{"scripts"} / "buildsystems" / "msbuild" / "applocal.ps1"}, - {fs::path{"scripts"} / "buildsystems" / "msbuild" / "vcpkg.targets"}, - {fs::path{"scripts"} / "buildsystems" / "vcpkg.cmake"}, - {fs::path{"scripts"} / "cmake" / "vcpkg_get_windows_sdk.cmake"}, - {fs::path{"scripts"} / "getWindowsSDK.ps1"}, - {fs::path{"scripts"} / "getProgramFilesPlatformBitness.ps1"}, - {fs::path{"scripts"} / "getProgramFiles32bit.ps1"}, - {fs::path{"scripts"} / "VcpkgPowershellUtils.ps1"}, + {fs::path {"scripts"} / "buildsystems" / "msbuild" / "applocal.ps1"}, + {fs::path {"scripts"} / "buildsystems" / "msbuild" / "vcpkg.targets"}, + {fs::path {"scripts"} / "buildsystems" / "vcpkg.cmake"}, + {fs::path {"scripts"} / "cmake" / "vcpkg_get_windows_sdk.cmake"}, }; for (const fs::path& file : integration_files_relative_to_root) diff --git a/toolsrc/src/vcpkg/globalstate.cpp b/toolsrc/src/vcpkg/globalstate.cpp index a4100acf7..a54c596fb 100644 --- a/toolsrc/src/vcpkg/globalstate.cpp +++ b/toolsrc/src/vcpkg/globalstate.cpp @@ -13,4 +13,48 @@ namespace vcpkg std::atomic<int> GlobalState::g_init_console_cp(0); std::atomic<int> GlobalState::g_init_console_output_cp(0); + std::atomic<bool> GlobalState::g_init_console_initialized(false); + + GlobalState::CtrlCStateMachine GlobalState::g_ctrl_c_state; + + GlobalState::CtrlCStateMachine::CtrlCStateMachine() : m_state(CtrlCState::normal) {} + + void GlobalState::CtrlCStateMachine::transition_to_spawn_process() noexcept + { + auto expected = CtrlCState::normal; + auto transitioned = m_state.compare_exchange_strong(expected, CtrlCState::blocked_on_child); + if (!transitioned) + { + // Ctrl-C was hit and is asynchronously executing on another thread + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + void GlobalState::CtrlCStateMachine::transition_from_spawn_process() noexcept + { + auto expected = CtrlCState::blocked_on_child; + auto transitioned = m_state.compare_exchange_strong(expected, CtrlCState::normal); + if (!transitioned) + { + // Ctrl-C was hit while blocked on the child process + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + void GlobalState::CtrlCStateMachine::transition_handle_ctrl_c() noexcept + { + auto prev_state = m_state.exchange(CtrlCState::exit_requested); + + if (prev_state == CtrlCState::normal) + { + // Not currently blocked on a child process and Ctrl-C has not been hit. + Checks::exit_fail(VCPKG_LINE_INFO); + } + else if (prev_state == CtrlCState::exit_requested) + { + // Ctrl-C was hit previously + } + else + { + // This is the case where we are currently blocked on a child process + } + } } diff --git a/toolsrc/src/vcpkg/help.cpp b/toolsrc/src/vcpkg/help.cpp index 743619937..5df878a91 100644 --- a/toolsrc/src/vcpkg/help.cpp +++ b/toolsrc/src/vcpkg/help.cpp @@ -7,6 +7,13 @@ #include <vcpkg/install.h> #include <vcpkg/remove.h> +// Write environment variable names as %VARIABLE% on Windows and $VARIABLE in *nix +#ifdef _WIN32 +#define ENVVAR(VARNAME) "%%" #VARNAME "%%" +#else +#define ENVVAR(VARNAME) "$" #VARNAME +#endif + namespace vcpkg::Help { struct Topic @@ -93,7 +100,7 @@ namespace vcpkg::Help "%s" // Integration help "\n" " vcpkg export <pkg>... [opt]... Exports a package\n" - " vcpkg edit <pkg> Open up a port for editing (uses %%EDITOR%%, default 'code')\n" + " vcpkg edit <pkg> Open up a port for editing (uses " ENVVAR(EDITOR) ", default 'code')\n" " vcpkg import <pkg> Import a pre-built library\n" " vcpkg create <pkg> <url>\n" " [archivename] Create a new package\n" @@ -104,10 +111,12 @@ namespace vcpkg::Help "\n" "Options:\n" " --triplet <t> Specify the target architecture triplet.\n" - " (default: %%VCPKG_DEFAULT_TRIPLET%%, see 'vcpkg help triplet')\n" + " (default: " ENVVAR(VCPKG_DEFAULT_TRIPLET) ", see 'vcpkg help triplet')\n" "\n" " --vcpkg-root <path> Specify the vcpkg root directory\n" - " (default: %%VCPKG_ROOT%%)\n" + " (default: " ENVVAR(VCPKG_ROOT) ")\n" + "\n" + " @response_file Specify a response file to provide additional parameters\n" "\n" "For more help (including examples) see the accompanying README.md.", Commands::Integrate::INTEGRATE_COMMAND_HELPSTRING); diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index fc336d6c7..1cfa2bf71 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -62,7 +62,7 @@ namespace vcpkg::Install auto files = fs.get_files_recursive(source_dir); for (auto&& file : files) { - const auto status = fs.status(file, ec); + const auto status = fs.symlink_status(file, ec); if (ec) { System::println(System::Color::error, "failed: %s: %s", file.u8string(), ec.message()); @@ -111,6 +111,23 @@ namespace vcpkg::Install output.push_back(Strings::format(R"(%s/%s)", destination_subdirectory, suffix)); break; } + case fs::file_type::symlink: + { + if (fs.exists(target)) + { + System::println(System::Color::warning, + "File %s was already present and will be overwritten", + target.u8string(), + ec.message()); + } + fs.copy_symlink(file, target, ec); + if (ec) + { + System::println(System::Color::error, "failed: %s: %s", target.u8string(), ec.message()); + } + output.push_back(Strings::format(R"(%s/%s)", destination_subdirectory, suffix)); + break; + } default: System::println(System::Color::error, "failed: %s: cannot handle file type", file.u8string()); break; @@ -462,14 +479,14 @@ namespace vcpkg::Install auto files = fs.read_lines(paths.listfile_path(bpgh)); if (auto p_lines = files.get()) { + std::map<std::string, std::string> config_files; std::map<std::string, std::vector<std::string>> library_targets; for (auto&& suffix : *p_lines) { - if (Strings::case_insensitive_ascii_find(suffix, "/share/") != suffix.end() && - suffix.substr(suffix.size() - 6) == ".cmake") + if (Strings::case_insensitive_ascii_contains(suffix, "/share/") && Strings::ends_with(suffix, ".cmake")) { - // File is inside the share folder + // CMake file is inside the share folder auto path = paths.installed / suffix; auto maybe_contents = fs.read_contents(path); auto find_package_name = path.parent_path().filename().u8string(); @@ -485,6 +502,21 @@ namespace vcpkg::Install ++next; } } + + auto filename = fs::u8path(suffix).filename().u8string(); + + if (Strings::ends_with(filename, "Config.cmake")) + { + auto root = filename.substr(0, filename.size() - 12); + if (Strings::case_insensitive_ascii_equals(root, find_package_name)) + config_files[find_package_name] = root; + } + else if (Strings::ends_with(filename, "-config.cmake")) + { + auto root = filename.substr(0, filename.size() - 13); + if (Strings::case_insensitive_ascii_equals(root, find_package_name)) + config_files[find_package_name] = root; + } } } @@ -497,11 +529,23 @@ namespace vcpkg::Install for (auto&& library_target_pair : library_targets) { + auto config_it = config_files.find(library_target_pair.first); + if (config_it != config_files.end()) + System::println(" find_package(%s CONFIG REQUIRED)", config_it->second); + else + System::println(" find_package(%s CONFIG REQUIRED)", library_target_pair.first); + + std::sort(library_target_pair.second.begin(), + library_target_pair.second.end(), + [](const std::string& l, const std::string& r) { + if (l.size() < r.size()) return true; + if (l.size() > r.size()) return false; + return l < r; + }); + if (library_target_pair.second.size() <= 4) { - System::println(" find_package(%s REQUIRED)\n" - " target_link_libraries(main PRIVATE %s)\n", - library_target_pair.first, + System::println(" target_link_libraries(main PRIVATE %s)\n", Strings::join(" ", library_target_pair.second)); } else @@ -509,10 +553,8 @@ namespace vcpkg::Install auto omitted = library_target_pair.second.size() - 4; library_target_pair.second.erase(library_target_pair.second.begin() + 4, library_target_pair.second.end()); - System::println(" find_package(%s REQUIRED)\n" - " # Note: %zd targets were omitted\n" + System::println(" # Note: %zd target(s) were omitted.\n" " target_link_libraries(main PRIVATE %s)\n", - library_target_pair.first, omitted, Strings::join(" ", library_target_pair.second)); } @@ -563,7 +605,10 @@ namespace vcpkg::Install Util::Enum::to_enum<Build::AllowDownloads>(!no_downloads), Build::CleanBuildtrees::NO, Build::CleanPackages::NO, - download_tool}; + download_tool, + GlobalState::g_binary_caching ? Build::BinaryCaching::YES : Build::BinaryCaching::NO, + Build::FailOnTombstone::NO, + }; auto all_ports = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); std::unordered_map<std::string, SourceControlFile> scf_map; diff --git a/toolsrc/src/vcpkg/metrics.cpp b/toolsrc/src/vcpkg/metrics.cpp index 8890c067f..2a73dba89 100644 --- a/toolsrc/src/vcpkg/metrics.cpp +++ b/toolsrc/src/vcpkg/metrics.cpp @@ -5,6 +5,7 @@ #include <vcpkg/base/chrono.h> #include <vcpkg/base/files.h> +#include <vcpkg/base/hash.h> #include <vcpkg/base/strings.h> #include <vcpkg/base/system.h> @@ -261,7 +262,7 @@ namespace vcpkg::Metrics const auto match = *next; if (match[0] != "00-00-00-00-00-00") { - return vcpkg::Commands::Hash::get_string_hash(match[0], "SHA256"); + return vcpkg::Hash::get_string_hash(match[0], "SHA256"); } ++next; } @@ -437,13 +438,14 @@ namespace vcpkg::Metrics const std::string cmd_line = Strings::format("start \"vcpkgmetricsuploader.exe\" \"%s\" \"%s\"", temp_folder_path_exe.u8string(), vcpkg_metrics_txt_path.u8string()); + System::cmd_execute_no_wait(cmd_line); #else auto escaped_path = Strings::escape_string(vcpkg_metrics_txt_path.u8string(), '\'', '\\'); const std::string cmd_line = Strings::format( R"((curl "https://dc.services.visualstudio.com/v2/track" -H "Content-Type: application/json" -X POST --data '@%s' >/dev/null 2>&1; rm '%s') &)", escaped_path, escaped_path); -#endif System::cmd_execute_clean(cmd_line); +#endif } } diff --git a/toolsrc/src/vcpkg/postbuildlint.cpp b/toolsrc/src/vcpkg/postbuildlint.cpp index 6fe11951f..650b6e3c9 100644 --- a/toolsrc/src/vcpkg/postbuildlint.cpp +++ b/toolsrc/src/vcpkg/postbuildlint.cpp @@ -16,9 +16,9 @@ using vcpkg::Build::PreBuildInfo; namespace vcpkg::PostBuildLint { - static auto has_extension_pred(const Files::Filesystem& fs, const std::string& ext) + static auto not_extension_pred(const Files::Filesystem& fs, const std::string& ext) { - return [&fs, ext](const fs::path& path) { return !fs.is_directory(path) && path.extension() == ext; }; + return [&fs, ext](const fs::path& path) { return fs.is_directory(path) || path.extension() != ext; }; } enum class LintStatus @@ -104,8 +104,8 @@ namespace vcpkg::PostBuildLint std::vector<fs::path> files_found = fs.get_files_recursive(debug_include_dir); - Util::unstable_keep_if( - files_found, [&fs](const fs::path& path) { return !fs.is_directory(path) && path.extension() != ".ifc"; }); + Util::erase_remove_if( + files_found, [&fs](const fs::path& path) { return fs.is_directory(path) || path.extension() == ".ifc"; }); if (!files_found.empty()) { @@ -206,7 +206,7 @@ namespace vcpkg::PostBuildLint static LintStatus check_for_dlls_in_lib_dir(const Files::Filesystem& fs, const fs::path& package_dir) { std::vector<fs::path> dlls = fs.get_files_recursive(package_dir / "lib"); - Util::unstable_keep_if(dlls, has_extension_pred(fs, ".dll")); + Util::erase_remove_if(dlls, not_extension_pred(fs, ".dll")); if (!dlls.empty()) { @@ -280,7 +280,7 @@ namespace vcpkg::PostBuildLint static LintStatus check_for_exes(const Files::Filesystem& fs, const fs::path& package_dir) { std::vector<fs::path> exes = fs.get_files_recursive(package_dir / "bin"); - Util::unstable_keep_if(exes, has_extension_pred(fs, ".exe")); + Util::erase_remove_if(exes, not_extension_pred(fs, ".exe")); if (!exes.empty()) { @@ -572,8 +572,8 @@ namespace vcpkg::PostBuildLint { std::vector<fs::path> empty_directories = fs.get_files_recursive(dir); - Util::unstable_keep_if(empty_directories, [&fs](const fs::path& current) { - return fs.is_directory(current) && fs.is_empty(current); + Util::erase_remove_if(empty_directories, [&fs](const fs::path& current) { + return !fs.is_directory(current) || !fs.is_empty(current); }); if (!empty_directories.empty()) @@ -617,7 +617,11 @@ namespace vcpkg::PostBuildLint const std::string cmd_line = Strings::format(R"("%s" /directives "%s")", dumpbin_exe.u8string(), lib.u8string()); System::ExitCodeAndOutput ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, + ec_data.exit_code == 0, + "Running command:\n %s\n failed with message:\n%s", + cmd_line, + ec_data.output); for (const BuildType& bad_build_type : bad_build_types) { @@ -703,12 +707,15 @@ namespace vcpkg::PostBuildLint static LintStatus check_no_files_in_dir(const Files::Filesystem& fs, const fs::path& dir) { std::vector<fs::path> misplaced_files = fs.get_files_non_recursive(dir); - Util::unstable_keep_if(misplaced_files, [&fs](const fs::path& path) { + Util::erase_remove_if(misplaced_files, [&fs](const fs::path& path) { const std::string filename = path.filename().generic_string(); if (Strings::case_insensitive_ascii_equals(filename.c_str(), "CONTROL") || Strings::case_insensitive_ascii_equals(filename.c_str(), "BUILD_INFO")) - return false; - return !fs.is_directory(path); + { + return true; + } + + return fs.is_directory(path); }); if (!misplaced_files.empty()) @@ -760,9 +767,9 @@ namespace vcpkg::PostBuildLint const fs::path release_bin_dir = package_dir / "bin"; std::vector<fs::path> debug_libs = fs.get_files_recursive(debug_lib_dir); - Util::unstable_keep_if(debug_libs, has_extension_pred(fs, ".lib")); + Util::erase_remove_if(debug_libs, not_extension_pred(fs, ".lib")); std::vector<fs::path> release_libs = fs.get_files_recursive(release_lib_dir); - Util::unstable_keep_if(release_libs, has_extension_pred(fs, ".lib")); + Util::erase_remove_if(release_libs, not_extension_pred(fs, ".lib")); if (!pre_build_info.build_type) error_count += check_matching_debug_and_release_binaries(debug_libs, release_libs); @@ -776,9 +783,9 @@ namespace vcpkg::PostBuildLint } std::vector<fs::path> debug_dlls = fs.get_files_recursive(debug_bin_dir); - Util::unstable_keep_if(debug_dlls, has_extension_pred(fs, ".dll")); + Util::erase_remove_if(debug_dlls, not_extension_pred(fs, ".dll")); std::vector<fs::path> release_dlls = fs.get_files_recursive(release_bin_dir); - Util::unstable_keep_if(release_dlls, has_extension_pred(fs, ".dll")); + Util::erase_remove_if(release_dlls, not_extension_pred(fs, ".dll")); switch (build_info.library_linkage) { diff --git a/toolsrc/src/vcpkg/remove.cpp b/toolsrc/src/vcpkg/remove.cpp index 13cc9325e..921a04c23 100644 --- a/toolsrc/src/vcpkg/remove.cpp +++ b/toolsrc/src/vcpkg/remove.cpp @@ -55,7 +55,7 @@ namespace vcpkg::Remove auto target = paths.installed / suffix; - const auto status = fs.status(target, ec); + const auto status = fs.symlink_status(target, ec); if (ec) { System::println(System::Color::error, "failed: status(%s): %s", target.u8string(), ec.message()); @@ -66,7 +66,7 @@ namespace vcpkg::Remove { dirs_touched.push_back(target); } - else if (fs::is_regular_file(status)) + else if (fs::is_regular_file(status) || fs::is_symlink(status)) { fs.remove(target, ec); if (ec) diff --git a/toolsrc/src/vcpkg/statusparagraph.cpp b/toolsrc/src/vcpkg/statusparagraph.cpp index 462d8d8ed..86946a31a 100644 --- a/toolsrc/src/vcpkg/statusparagraph.cpp +++ b/toolsrc/src/vcpkg/statusparagraph.cpp @@ -105,7 +105,7 @@ namespace vcpkg { dep.erase(std::find(dep.begin(), dep.end(), '['), dep.end()); } - Util::unstable_keep_if(deps, [&](auto&& e) { return e != l_spec.name(); }); + Util::erase_remove_if(deps, [&](auto&& e) { return e == l_spec.name(); }); // </hack> Util::sort_unique_erase(deps); diff --git a/toolsrc/src/vcpkg/tools.cpp b/toolsrc/src/vcpkg/tools.cpp new file mode 100644 index 000000000..f4ee2d653 --- /dev/null +++ b/toolsrc/src/vcpkg/tools.cpp @@ -0,0 +1,533 @@ +#include "pch.h" + +#include <vcpkg/archives.h> +#include <vcpkg/tools.h> +#include <vcpkg/vcpkgpaths.h> + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/downloads.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/optional.h> +#include <vcpkg/base/stringrange.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> + +namespace vcpkg +{ + struct ToolData + { + std::array<int, 3> version; + fs::path exe_path; + std::string url; + fs::path download_path; + bool is_archive; + fs::path tool_dir_path; + std::string sha512; + }; + + static Optional<std::array<int, 3>> parse_version_string(const std::string& version_as_string) + { + static const std::regex RE(R"###((\d+)\.(\d+)\.(\d+))###"); + + std::match_results<std::string::const_iterator> match; + const auto found = std::regex_search(version_as_string, match, RE); + if (!found) + { + return {}; + } + + const int d1 = atoi(match[1].str().c_str()); + const int d2 = atoi(match[2].str().c_str()); + const int d3 = atoi(match[3].str().c_str()); + const std::array<int, 3> result = {d1, d2, d3}; + return result; + } + + static ToolData parse_tool_data_from_xml(const VcpkgPaths& paths, const std::string& tool) + { +#if defined(_WIN32) + static constexpr StringLiteral OS_STRING = "windows"; +#elif defined(__APPLE__) + static constexpr StringLiteral OS_STRING = "osx"; +#elif defined(__linux__) + static constexpr StringLiteral OS_STRING = "linux"; +#else + return ToolData{}; +#endif + +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + static const std::string XML_VERSION = "2"; + static const fs::path XML_PATH = paths.scripts / "vcpkgTools.xml"; + static const std::regex XML_VERSION_REGEX{R"###(<tools[\s]+version="([^"]+)">)###"}; + static const std::string XML = paths.get_filesystem().read_contents(XML_PATH).value_or_exit(VCPKG_LINE_INFO); + std::smatch match_xml_version; + const bool has_xml_version = std::regex_search(XML.cbegin(), XML.cend(), match_xml_version, XML_VERSION_REGEX); + Checks::check_exit(VCPKG_LINE_INFO, + has_xml_version, + R"(Could not find <tools version="%s"> in %s)", + XML_VERSION, + XML_PATH.generic_string()); + Checks::check_exit(VCPKG_LINE_INFO, + XML_VERSION == match_xml_version[1], + "Expected %s version: [%s], but was [%s]. Please re-run bootstrap-vcpkg.", + XML_PATH.generic_string(), + XML_VERSION, + match_xml_version[1]); + + const std::regex tool_regex{Strings::format(R"###(<tool[\s]+name="%s"[\s]+os="%s">)###", tool, OS_STRING)}; + std::smatch match_tool_entry; + const bool has_tool_entry = std::regex_search(XML.cbegin(), XML.cend(), match_tool_entry, tool_regex); + Checks::check_exit(VCPKG_LINE_INFO, + has_tool_entry, + "Could not find entry for tool [%s] in %s", + tool, + XML_PATH.generic_string()); + + const std::string tool_data = + StringRange::find_exactly_one_enclosed(XML, match_tool_entry[0], "</tool>").to_string(); + const std::string version_as_string = + StringRange::find_exactly_one_enclosed(tool_data, "<version>", "</version>").to_string(); + const std::string exe_relative_path = + StringRange::find_exactly_one_enclosed(tool_data, "<exeRelativePath>", "</exeRelativePath>").to_string(); + const std::string url = StringRange::find_exactly_one_enclosed(tool_data, "<url>", "</url>").to_string(); + const std::string sha512 = + StringRange::find_exactly_one_enclosed(tool_data, "<sha512>", "</sha512>").to_string(); + auto archive_name = StringRange::find_at_most_one_enclosed(tool_data, "<archiveName>", "</archiveName>"); + + const Optional<std::array<int, 3>> version = parse_version_string(version_as_string); + Checks::check_exit(VCPKG_LINE_INFO, + version.has_value(), + "Could not parse version for tool %s. Version string was: %s", + tool, + version_as_string); + + const std::string tool_dir_name = Strings::format("%s-%s-%s", tool, version_as_string, OS_STRING); + const fs::path tool_dir_path = paths.tools / tool_dir_name; + const fs::path exe_path = tool_dir_path / exe_relative_path; + + return ToolData{*version.get(), + exe_path, + url, + paths.downloads / archive_name.value_or(exe_relative_path).to_string(), + archive_name.has_value(), + tool_dir_path, + sha512}; +#endif + } + + struct PathAndVersion + { + fs::path path; + std::string version; + }; + + static Optional<PathAndVersion> find_first_with_sufficient_version(const std::vector<PathAndVersion>& candidates, + const std::array<int, 3>& expected_version) + { + const auto it = Util::find_if(candidates, [&](const PathAndVersion& candidate) { + const auto parsed_version = parse_version_string(candidate.version); + if (!parsed_version.has_value()) + { + return false; + } + + const std::array<int, 3> actual_version = *parsed_version.get(); + return actual_version[0] > expected_version[0] || + (actual_version[0] == expected_version[0] && actual_version[1] > expected_version[1]) || + (actual_version[0] == expected_version[0] && actual_version[1] == expected_version[1] && + actual_version[2] >= expected_version[2]); + }); + + if (it == candidates.cend()) + { + return nullopt; + } + + return *it; + } + + struct VersionProvider + { + virtual Optional<std::string> get_version(const fs::path& path_to_exe) const = 0; + + std::vector<PathAndVersion> get_versions(const std::vector<fs::path>& candidate_paths) const + { + auto&& fs = Files::get_real_filesystem(); + + std::vector<PathAndVersion> output; + for (auto&& p : candidate_paths) + { + if (!fs.exists(p)) continue; + auto maybe_version = this->get_version(p); + if (const auto version = maybe_version.get()) + { + output.emplace_back(PathAndVersion{p, *version}); + return output; + } + } + + return output; + } + }; + + static fs::path fetch_tool(const VcpkgPaths& paths, const std::string& tool_name, const ToolData& tool_data) + { + const std::array<int, 3>& version = tool_data.version; + const std::string version_as_string = Strings::format("%d.%d.%d", version[0], version[1], version[2]); + Checks::check_exit(VCPKG_LINE_INFO, + !tool_data.url.empty(), + "A suitable version of %s was not found (required v%s) and unable to automatically " + "download a portable one. Please install a newer version of %s.", + tool_name, + version_as_string, + tool_name); + System::println("A suitable version of %s was not found (required v%s). Downloading portable %s v%s...", + tool_name, + version_as_string, + tool_name, + version_as_string); + auto& fs = paths.get_filesystem(); + if (!fs.exists(tool_data.download_path)) + { + System::println("Downloading %s...", tool_name); + System::println(" %s -> %s", tool_data.url, tool_data.download_path.string()); + Downloads::download_file(fs, tool_data.url, tool_data.download_path, tool_data.sha512); + } + else + { + Downloads::verify_downloaded_file_hash(fs, tool_data.url, tool_data.download_path, tool_data.sha512); + } + + if (tool_data.is_archive) + { + System::println("Extracting %s...", tool_name); + Archives::extract_archive(paths, tool_data.download_path, tool_data.tool_dir_path); + } + else + { + std::error_code ec; + fs.create_directories(tool_data.exe_path.parent_path(), ec); + fs.rename(tool_data.download_path, tool_data.exe_path, ec); + } + + Checks::check_exit(VCPKG_LINE_INFO, + fs.exists(tool_data.exe_path), + "Expected %s to exist after fetching", + tool_data.exe_path.u8string()); + + return tool_data.exe_path; + } + + static PathAndVersion fetch_tool(const VcpkgPaths& paths, + const std::string& tool_name, + const ToolData& tool_data, + const VersionProvider& version_provider) + { + const auto downloaded_path = fetch_tool(paths, tool_name, tool_data); + const auto downloaded_version = version_provider.get_version(downloaded_path).value_or_exit(VCPKG_LINE_INFO); + return {downloaded_path, downloaded_version}; + } + + namespace CMake + { + struct CmakeVersionProvider : VersionProvider + { + Optional<std::string> get_version(const fs::path& path_to_exe) const override + { + const std::string cmd = Strings::format(R"("%s" --version)", path_to_exe.u8string()); + const auto rc = System::cmd_execute_and_capture_output(cmd); + if (rc.exit_code != 0) + { + return nullopt; + } + + /* Sample output: + cmake version 3.10.2 + + CMake suite maintained and supported by Kitware (kitware.com/cmake). + */ + return StringRange::find_exactly_one_enclosed(rc.output, "cmake version ", "\n").to_string(); + } + }; + + static PathAndVersion get_path(const VcpkgPaths& paths) + { + std::vector<fs::path> candidate_paths; +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "cmake"); + candidate_paths.push_back(TOOL_DATA.exe_path); +#else + static const ToolData TOOL_DATA = ToolData{{3, 5, 1}, ""}; +#endif + const std::vector<fs::path> from_path = paths.get_filesystem().find_from_PATH("cmake"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const auto pf = program_files_32_bit.get()) + candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); + + const CmakeVersionProvider version_provider{}; + const std::vector<PathAndVersion> candidates_with_versions = version_provider.get_versions(candidate_paths); + const auto maybe_path = find_first_with_sufficient_version(candidates_with_versions, TOOL_DATA.version); + if (const auto p = maybe_path.get()) + { + return *p; + } + + return fetch_tool(paths, Tools::CMAKE, TOOL_DATA, version_provider); + } + } + + static fs::path get_7za_path(const VcpkgPaths& paths) + { +#if defined(_WIN32) + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "7zip"); + if (!paths.get_filesystem().exists(TOOL_DATA.exe_path)) + { + return fetch_tool(paths, "7zip", TOOL_DATA); + } + return TOOL_DATA.exe_path; +#else + Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot download 7zip for non-Windows platforms."); +#endif + } + + namespace Ninja + { + struct NinjaVersionProvider : VersionProvider + { + Optional<std::string> get_version(const fs::path& path_to_exe) const override + { + const std::string cmd = Strings::format(R"("%s" --version)", path_to_exe.u8string()); + const auto rc = System::cmd_execute_and_capture_output(cmd); + if (rc.exit_code != 0) + { + return nullopt; + } + + /* Sample output: + 1.8.2 + */ + return rc.output; + } + }; + + static PathAndVersion get_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "ninja"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + const std::vector<fs::path> from_path = paths.get_filesystem().find_from_PATH("ninja"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const NinjaVersionProvider version_provider{}; + const std::vector<PathAndVersion> candidates_with_versions = version_provider.get_versions(candidate_paths); + const auto maybe_path = find_first_with_sufficient_version(candidates_with_versions, TOOL_DATA.version); + if (const auto p = maybe_path.get()) + { + return *p; + } + + return fetch_tool(paths, Tools::NINJA, TOOL_DATA, version_provider); + } + } + + namespace Nuget + { + struct NugetVersionProvider : VersionProvider + { + Optional<std::string> get_version(const fs::path& path_to_exe) const override + { + const std::string cmd = Strings::format(R"("%s")", path_to_exe.u8string()); + const auto rc = System::cmd_execute_and_capture_output(cmd); + if (rc.exit_code != 0) + { + return nullopt; + } + + /* Sample output: + NuGet Version: 4.6.2.5055 + usage: NuGet <command> [args] [options] + Type 'NuGet help <command>' for help on a specific command. + + [[[List of available commands follows]]] + */ + return StringRange::find_exactly_one_enclosed(rc.output, "NuGet Version: ", "\n").to_string(); + } + }; + + static PathAndVersion get_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "nuget"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + const std::vector<fs::path> from_path = paths.get_filesystem().find_from_PATH("nuget"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const NugetVersionProvider version_provider{}; + const std::vector<PathAndVersion> candidates_with_versions = version_provider.get_versions(candidate_paths); + const auto maybe_path = find_first_with_sufficient_version(candidates_with_versions, TOOL_DATA.version); + if (const auto p = maybe_path.get()) + { + return *p; + } + + return fetch_tool(paths, Tools::NUGET, TOOL_DATA, version_provider); + } + } + + namespace Git + { + struct GitVersionProvider : VersionProvider + { + Optional<std::string> get_version(const fs::path& path_to_exe) const override + { + const std::string cmd = Strings::format(R"("%s" --version)", path_to_exe.u8string()); + const auto rc = System::cmd_execute_and_capture_output(cmd); + if (rc.exit_code != 0) + { + return nullopt; + } + + /* Sample output: + git version 2.17.1.windows.2 + */ + const auto idx = rc.output.find("git version "); + Checks::check_exit(VCPKG_LINE_INFO, + idx != std::string::npos, + "Unexpected format of git version string: %s", + rc.output); + return rc.output.substr(idx); + } + }; + + static PathAndVersion get_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "git"); + + std::vector<fs::path> candidate_paths; +#if defined(_WIN32) + candidate_paths.push_back(TOOL_DATA.exe_path); +#endif + const std::vector<fs::path> from_path = paths.get_filesystem().find_from_PATH("git"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); + + const GitVersionProvider version_provider{}; + const std::vector<PathAndVersion> candidates_with_versions = version_provider.get_versions(candidate_paths); + const auto maybe_path = find_first_with_sufficient_version(candidates_with_versions, TOOL_DATA.version); + if (const auto p = maybe_path.get()) + { + return *p; + } + + return fetch_tool(paths, Tools::GIT, TOOL_DATA, version_provider); + } + } + + namespace IfwInstallerBase + { + struct IfwInstallerBaseVersionProvider : VersionProvider + { + Optional<std::string> get_version(const fs::path& path_to_exe) const override + { + const std::string cmd = Strings::format(R"("%s" --framework-version)", path_to_exe.u8string()); + const auto rc = System::cmd_execute_and_capture_output(cmd); + if (rc.exit_code != 0) + { + return nullopt; + } + + /* Sample output: + 3.1.81 + */ + return rc.output; + } + }; + + static PathAndVersion get_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "installerbase"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + // TODO: Uncomment later + // const std::vector<fs::path> from_path = Files::find_from_PATH("installerbase"); + // candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / + // "Tools" / "QtInstallerFramework" / "3.1" / "bin" / "installerbase.exe"); + // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / + // "QtIFW-3.1.0" / "bin" / "installerbase.exe"); + + const IfwInstallerBaseVersionProvider version_provider{}; + const std::vector<PathAndVersion> candidates_with_versions = version_provider.get_versions(candidate_paths); + const auto maybe_path = find_first_with_sufficient_version(candidates_with_versions, TOOL_DATA.version); + if (const auto p = maybe_path.get()) + { + return *p; + } + + return fetch_tool(paths, Tools::IFW_INSTALLER_BASE, TOOL_DATA, version_provider); + } + } + + struct ToolCacheImpl final : ToolCache + { + vcpkg::Cache<std::string, fs::path> path_only_cache; + vcpkg::Cache<std::string, PathAndVersion> path_version_cache; + + 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 IfwInstallerBase::get_path(paths).path.parent_path() / "binarycreator.exe"; + if (tool == Tools::IFW_REPOGEN) + return IfwInstallerBase::get_path(paths).path.parent_path() / "repogen.exe"; + + // For other tools, we simply always auto-download them. + const ToolData tool_data = parse_tool_data_from_xml(paths, tool); + if (paths.get_filesystem().exists(tool_data.exe_path)) + { + return tool_data.exe_path; + } + return fetch_tool(paths, tool, tool_data); + }); + } + + const PathAndVersion& get_tool_pathversion(const VcpkgPaths& paths, const std::string& tool) const + { + return path_version_cache.get_lazy(tool, [&]() { + if (tool == Tools::CMAKE) return CMake::get_path(paths); + if (tool == Tools::GIT) return Git::get_path(paths); + if (tool == Tools::NINJA) return Ninja::get_path(paths); + if (tool == Tools::NUGET) return Nuget::get_path(paths); + if (tool == Tools::IFW_INSTALLER_BASE) return IfwInstallerBase::get_path(paths); + + Checks::exit_with_message(VCPKG_LINE_INFO, "Finding version for %s is not implemented yet.", tool); + }); + } + + virtual const std::string& get_tool_version(const VcpkgPaths& paths, const std::string& tool) const override + { + return get_tool_pathversion(paths, tool).version; + } + }; + + std::unique_ptr<ToolCache> get_tool_cache() { return std::make_unique<ToolCacheImpl>(); } +} diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp index 8909e1552..5b3cf9ef1 100644 --- a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp +++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp @@ -51,11 +51,29 @@ namespace vcpkg std::vector<std::string> v; for (int i = 1; i < argc; ++i) { + std::string arg; #if defined(_WIN32) - v.push_back(Strings::to_utf8(argv[i])); + arg = Strings::to_utf8(argv[i]); #else - v.push_back(argv[i]); + arg = argv[i]; #endif + // Response file? + if (arg.size() > 0 && arg[0] == '@') + { + arg.erase(arg.begin()); + const auto& fs = Files::get_real_filesystem(); + auto lines = fs.read_lines(fs::u8path(arg)); + if (!lines.has_value()) + { + System::println(System::Color::error, "Error: Could not open response file %s", arg); + Checks::exit_fail(VCPKG_LINE_INFO); + } + std::copy(lines.get()->begin(), lines.get()->end(), std::back_inserter(v)); + } + else + { + v.emplace_back(std::move(arg)); + } } return VcpkgCmdArguments::create_from_arg_sequence(v.data(), v.data() + v.size()); diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 0903c2d76..9a51818e8 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -9,6 +9,7 @@ #include <vcpkg/metrics.h> #include <vcpkg/packagespec.h> #include <vcpkg/vcpkgpaths.h> +#include <vcpkg/visualstudio.h> namespace vcpkg { @@ -39,6 +40,7 @@ namespace vcpkg paths.triplets = paths.root / "triplets"; paths.scripts = paths.root / "scripts"; + paths.tools = paths.downloads / "tools"; paths.buildsystems = paths.scripts / "buildsystems"; paths.buildsystems_msbuild_targets = paths.buildsystems / "msbuild" / "vcpkg.targets"; @@ -91,7 +93,13 @@ namespace vcpkg const fs::path& VcpkgPaths::get_tool_exe(const std::string& tool) const { - return this->tool_paths.get_lazy(tool, [&]() { return Commands::Fetch::get_tool_path(*this, tool); }); + if (!m_tool_cache) m_tool_cache = get_tool_cache(); + return m_tool_cache->get_tool_path(*this, tool); + } + const std::string& VcpkgPaths::get_tool_version(const std::string& tool) const + { + if (!m_tool_cache) m_tool_cache = get_tool_cache(); + return m_tool_cache->get_tool_version(*this, tool); } const Toolset& VcpkgPaths::get_toolset(const Build::PreBuildInfo& prebuildinfo) const @@ -113,9 +121,11 @@ namespace vcpkg return external_toolset; } - // Invariant: toolsets are non-empty and sorted with newest at back() +#if !defined(_WIN32) + Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot build windows triplets from non-windows."); +#else const std::vector<Toolset>& vs_toolsets = - this->toolsets.get_lazy([this]() { return Commands::Fetch::find_toolset_instances(*this); }); + this->toolsets.get_lazy([this]() { return VisualStudio::find_toolset_instances_preferred_first(*this); }); std::vector<const Toolset*> candidates = Util::element_pointers(vs_toolsets); const auto tsv = prebuildinfo.platform_toolset.get(); @@ -127,8 +137,8 @@ namespace vcpkg if (tsv && vsp) { - Util::stable_keep_if( - candidates, [&](const Toolset* t) { return *tsv == t->version && *vsp == t->visual_studio_root_path; }); + Util::erase_remove_if( + candidates, [&](const Toolset* t) { return *tsv != t->version || *vsp != t->visual_studio_root_path; }); Checks::check_exit(VCPKG_LINE_INFO, !candidates.empty(), "Could not find Visual Studio instance at %s with %s toolset.", @@ -141,7 +151,7 @@ namespace vcpkg if (tsv) { - Util::stable_keep_if(candidates, [&](const Toolset* t) { return *tsv == t->version; }); + Util::erase_remove_if(candidates, [&](const Toolset* t) { return *tsv != t->version; }); Checks::check_exit( VCPKG_LINE_INFO, !candidates.empty(), "Could not find Visual Studio instance with %s toolset.", *tsv); } @@ -149,8 +159,8 @@ namespace vcpkg if (vsp) { const fs::path vs_root_path = *vsp; - Util::stable_keep_if(candidates, - [&](const Toolset* t) { return vs_root_path == t->visual_studio_root_path; }); + Util::erase_remove_if(candidates, + [&](const Toolset* t) { return vs_root_path != t->visual_studio_root_path; }); Checks::check_exit(VCPKG_LINE_INFO, !candidates.empty(), "Could not find Visual Studio instance at %s.", @@ -159,6 +169,8 @@ namespace vcpkg Checks::check_exit(VCPKG_LINE_INFO, !candidates.empty(), "No suitable Visual Studio instances were found"); return *candidates.front(); + +#endif } Files::Filesystem& VcpkgPaths::get_filesystem() const { return Files::get_real_filesystem(); } diff --git a/toolsrc/src/vcpkg/visualstudio.cpp b/toolsrc/src/vcpkg/visualstudio.cpp new file mode 100644 index 000000000..83a530a10 --- /dev/null +++ b/toolsrc/src/vcpkg/visualstudio.cpp @@ -0,0 +1,326 @@ +#include "pch.h" + +#if defined(_WIN32) + +#include <vcpkg/base/sortedvector.h> +#include <vcpkg/base/stringrange.h> +#include <vcpkg/base/util.h> +#include <vcpkg/visualstudio.h> + +namespace vcpkg::VisualStudio +{ + static constexpr CStringView V_120 = "v120"; + static constexpr CStringView V_140 = "v140"; + static constexpr CStringView V_141 = "v141"; + + struct VisualStudioInstance + { + enum class ReleaseType + { + STABLE, + PRERELEASE, + LEGACY + }; + + static std::string release_type_to_string(const ReleaseType& release_type) + { + switch (release_type) + { + case ReleaseType::STABLE: return "STABLE"; + case ReleaseType::PRERELEASE: return "PRERELEASE"; + case ReleaseType::LEGACY: return "LEGACY"; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + static bool preferred_first_comparator(const VisualStudioInstance& left, const VisualStudioInstance& right) + { + const auto get_preference_weight = [](const ReleaseType& type) -> int { + switch (type) + { + case ReleaseType::STABLE: return 3; + case ReleaseType::PRERELEASE: return 2; + case ReleaseType::LEGACY: return 1; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + }; + + if (left.release_type != right.release_type) + { + return get_preference_weight(left.release_type) > get_preference_weight(right.release_type); + } + + return left.version > right.version; + } + + VisualStudioInstance(fs::path&& root_path, std::string&& version, const ReleaseType& release_type) + : root_path(std::move(root_path)), version(std::move(version)), release_type(release_type) + { + } + + fs::path root_path; + std::string version; + ReleaseType release_type; + + std::string to_string() const + { + return Strings::format("%s, %s, %s", root_path.u8string(), version, release_type_to_string(release_type)); + } + + std::string major_version() const { return version.substr(0, 2); } + }; + + static std::vector<VisualStudioInstance> get_visual_studio_instances_internal(const VcpkgPaths& paths) + { + const auto& fs = paths.get_filesystem(); + std::vector<VisualStudioInstance> instances; + + const auto& program_files_32_bit = System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO); + + // Instances from vswhere + const fs::path vswhere_exe = program_files_32_bit / "Microsoft Visual Studio" / "Installer" / "vswhere.exe"; + if (fs.exists(vswhere_exe)) + { + const auto code_and_output = System::cmd_execute_and_capture_output( + Strings::format(R"("%s" -all -prerelease -legacy -products * -format xml)", vswhere_exe.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, + code_and_output.exit_code == 0, + "Running vswhere.exe failed with message:\n%s", + code_and_output.output); + + const auto instance_entries = + StringRange::find_all_enclosed(code_and_output.output, "<instance>", "</instance>"); + for (const StringRange& instance : instance_entries) + { + auto maybe_is_prerelease = + StringRange::find_at_most_one_enclosed(instance, "<isPrerelease>", "</isPrerelease>"); + + VisualStudioInstance::ReleaseType release_type = VisualStudioInstance::ReleaseType::LEGACY; + if (const auto p = maybe_is_prerelease.get()) + { + const auto s = p->to_string(); + if (s == "0") + release_type = VisualStudioInstance::ReleaseType::STABLE; + else if (s == "1") + release_type = VisualStudioInstance::ReleaseType::PRERELEASE; + else + Checks::unreachable(VCPKG_LINE_INFO); + } + + instances.emplace_back( + StringRange::find_exactly_one_enclosed(instance, "<installationPath>", "</installationPath>") + .to_string(), + StringRange::find_exactly_one_enclosed(instance, "<installationVersion>", "</installationVersion>") + .to_string(), + release_type); + } + } + + const auto append_if_has_cl = [&](fs::path&& path_root) { + const auto cl_exe = path_root / "VC" / "bin" / "cl.exe"; + const auto vcvarsall_bat = path_root / "VC" / "vcvarsall.bat"; + + if (fs.exists(cl_exe) && fs.exists(vcvarsall_bat)) + instances.emplace_back(std::move(path_root), "14.0", VisualStudioInstance::ReleaseType::LEGACY); + }; + + // VS2015 instance from environment variable + auto maybe_vs140_comntools = System::get_environment_variable("vs140comntools"); + if (const auto path_as_string = maybe_vs140_comntools.get()) + { + // We want lexically_normal(), but it is not available + // Correct root path might be 2 or 3 levels up, depending on if the path has trailing backslash. Try both. + auto common7_tools = fs::path{*path_as_string}; + append_if_has_cl(fs::path{*path_as_string}.parent_path().parent_path()); + append_if_has_cl(fs::path{*path_as_string}.parent_path().parent_path().parent_path()); + } + + // VS2015 instance from Program Files + append_if_has_cl(program_files_32_bit / "Microsoft Visual Studio 14.0"); + + return instances; + } + + std::vector<std::string> get_visual_studio_instances(const VcpkgPaths& paths) + { + std::vector<VisualStudioInstance> sorted{get_visual_studio_instances_internal(paths)}; + std::sort(sorted.begin(), sorted.end(), VisualStudioInstance::preferred_first_comparator); + return Util::fmap(sorted, [](const VisualStudioInstance& instance) { return instance.to_string(); }); + } + + std::vector<Toolset> find_toolset_instances_preferred_first(const VcpkgPaths& paths) + { + using CPU = System::CPUArchitecture; + + const auto& fs = paths.get_filesystem(); + + // Note: this will contain a mix of vcvarsall.bat locations and dumpbin.exe locations. + std::vector<fs::path> paths_examined; + + std::vector<Toolset> found_toolsets; + std::vector<Toolset> excluded_toolsets; + + const SortedVector<VisualStudioInstance> sorted{get_visual_studio_instances_internal(paths), + VisualStudioInstance::preferred_first_comparator}; + + const bool v140_is_available = Util::find_if(sorted, [&](const VisualStudioInstance& vs_instance) { + return vs_instance.major_version() == "14"; + }) != sorted.end(); + + for (const VisualStudioInstance& vs_instance : sorted) + { + const std::string major_version = vs_instance.major_version(); + if (major_version >= "15") + { + const fs::path vc_dir = vs_instance.root_path / "VC"; + + // Skip any instances that do not have vcvarsall. + const fs::path vcvarsall_dir = vc_dir / "Auxiliary" / "Build"; + const fs::path vcvarsall_bat = vcvarsall_dir / "vcvarsall.bat"; + paths_examined.push_back(vcvarsall_bat); + if (!fs.exists(vcvarsall_bat)) continue; + + // Get all supported architectures + std::vector<ToolsetArchOption> supported_architectures; + if (fs.exists(vcvarsall_dir / "vcvars32.bat")) + supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); + if (fs.exists(vcvarsall_dir / "vcvars64.bat")) + supported_architectures.push_back({"amd64", CPU::X64, CPU::X64}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_amd64.bat")) + supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_arm.bat")) + supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_arm64.bat")) + supported_architectures.push_back({"x86_arm64", CPU::X86, CPU::ARM64}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_x86.bat")) + supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm.bat")) + supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm64.bat")) + supported_architectures.push_back({"amd64_arm64", CPU::X64, CPU::ARM64}); + + // Locate the "best" MSVC toolchain version + const fs::path msvc_path = vc_dir / "Tools" / "MSVC"; + std::vector<fs::path> msvc_subdirectories = fs.get_files_non_recursive(msvc_path); + Util::erase_remove_if(msvc_subdirectories, + [&fs](const fs::path& path) { return !fs.is_directory(path); }); + + // Sort them so that latest comes first + std::sort( + msvc_subdirectories.begin(), + msvc_subdirectories.end(), + [](const fs::path& left, const fs::path& right) { return left.filename() > right.filename(); }); + + for (const fs::path& subdir : msvc_subdirectories) + { + const fs::path dumpbin_path = subdir / "bin" / "HostX86" / "x86" / "dumpbin.exe"; + paths_examined.push_back(dumpbin_path); + if (fs.exists(dumpbin_path)) + { + const Toolset v141_toolset{ + vs_instance.root_path, dumpbin_path, vcvarsall_bat, {}, V_141, supported_architectures}; + + const auto english_language_pack = dumpbin_path.parent_path() / "1033"; + + if (!fs.exists(english_language_pack)) + { + excluded_toolsets.push_back(v141_toolset); + break; + } + + found_toolsets.push_back(v141_toolset); + + if (v140_is_available) + { + const Toolset v140_toolset{vs_instance.root_path, + dumpbin_path, + vcvarsall_bat, + {"-vcvars_ver=14.0"}, + V_140, + supported_architectures}; + found_toolsets.push_back(v140_toolset); + } + + break; + } + } + + continue; + } + + if (major_version == "14" || major_version == "12") + { + const fs::path vcvarsall_bat = vs_instance.root_path / "VC" / "vcvarsall.bat"; + + paths_examined.push_back(vcvarsall_bat); + if (fs.exists(vcvarsall_bat)) + { + const fs::path vs_dumpbin_exe = vs_instance.root_path / "VC" / "bin" / "dumpbin.exe"; + paths_examined.push_back(vs_dumpbin_exe); + + const fs::path vs_bin_dir = vcvarsall_bat.parent_path() / "bin"; + std::vector<ToolsetArchOption> supported_architectures; + if (fs.exists(vs_bin_dir / "vcvars32.bat")) + supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); + if (fs.exists(vs_bin_dir / "amd64\\vcvars64.bat")) + supported_architectures.push_back({"x64", CPU::X64, CPU::X64}); + if (fs.exists(vs_bin_dir / "x86_amd64\\vcvarsx86_amd64.bat")) + supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); + if (fs.exists(vs_bin_dir / "x86_arm\\vcvarsx86_arm.bat")) + supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); + if (fs.exists(vs_bin_dir / "amd64_x86\\vcvarsamd64_x86.bat")) + supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); + if (fs.exists(vs_bin_dir / "amd64_arm\\vcvarsamd64_arm.bat")) + supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); + + if (fs.exists(vs_dumpbin_exe)) + { + const Toolset toolset = {vs_instance.root_path, + vs_dumpbin_exe, + vcvarsall_bat, + {}, + major_version == "14" ? V_140 : V_120, + supported_architectures}; + + const auto english_language_pack = vs_dumpbin_exe.parent_path() / "1033"; + + if (!fs.exists(english_language_pack)) + { + excluded_toolsets.push_back(toolset); + break; + } + + found_toolsets.push_back(toolset); + } + } + } + } + + if (!excluded_toolsets.empty()) + { + System::println( + System::Color::warning, + "Warning: The following VS instances are excluded because the English language pack is unavailable."); + for (const Toolset& toolset : excluded_toolsets) + { + System::println(" %s", toolset.visual_studio_root_path.u8string()); + } + System::println(System::Color::warning, "Please install the English language pack."); + } + + if (found_toolsets.empty()) + { + System::println(System::Color::error, "Could not locate a complete toolset."); + System::println("The following paths were examined:"); + for (const fs::path& path : paths_examined) + { + System::println(" %s", path.u8string()); + } + Checks::exit_fail(VCPKG_LINE_INFO); + } + + return found_toolsets; + } +} + +#endif |
