diff options
| author | Alexander Karatarakis <alkarata@microsoft.com> | 2016-09-18 20:50:08 -0700 |
|---|---|---|
| committer | Alexander Karatarakis <alkarata@microsoft.com> | 2016-09-18 20:54:03 -0700 |
| commit | ccca198c1b1730b0241911cb56dc8e3504958b2a (patch) | |
| tree | a2dd9b8b087a09afdcecc5cbb3377bed15127eb2 /toolsrc/src | |
| download | vcpkg-ccca198c1b1730b0241911cb56dc8e3504958b2a.tar.gz vcpkg-ccca198c1b1730b0241911cb56dc8e3504958b2a.zip | |
Initial commit
Diffstat (limited to 'toolsrc/src')
28 files changed, 4129 insertions, 0 deletions
diff --git a/toolsrc/src/BinaryParagraph.cpp b/toolsrc/src/BinaryParagraph.cpp new file mode 100644 index 000000000..274bd879e --- /dev/null +++ b/toolsrc/src/BinaryParagraph.cpp @@ -0,0 +1,82 @@ +#include "BinaryParagraph.h" +#include "vcpkglib_helpers.h" +#include "vcpkg_Checks.h" + +using namespace vcpkg::details; + +namespace vcpkg +{ + BinaryParagraph::BinaryParagraph() = default; + + BinaryParagraph::BinaryParagraph(const std::unordered_map<std::string, std::string>& fields) + { + details::required_field(fields, name, "Package"); + required_field(fields, version, "Version"); + required_field(fields, target_triplet.value, "Architecture"); + { + std::string multi_arch; + required_field(fields, multi_arch, "Multi-Arch"); + Checks::check_throw(multi_arch == "same", "Multi-Arch must be 'same' but was %s", multi_arch); + } + optional_field(fields, description, "Description"); + std::string deps; + optional_field(fields, deps, "Depends"); + if (!deps.empty()) + { + depends.clear(); + parse_depends(deps, depends); + } + optional_field(fields, maintainer, "Maintainer"); + } + + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const triplet& target_triplet) + { + this->name = spgh.name; + this->version = spgh.version; + this->description = spgh.description; + this->maintainer = spgh.maintainer; + this->depends = spgh.depends; + this->target_triplet = target_triplet; + } + + std::string BinaryParagraph::displayname() const + { + return Strings::format("%s:%s", this->name, this->target_triplet); + } + + std::string BinaryParagraph::dir() const + { + return Strings::format("%s_%s", this->name, this->target_triplet); + } + + std::string BinaryParagraph::fullstem() const + { + return Strings::format("%s_%s_%s", this->name, this->version, this->target_triplet); + } + + std::ostream& operator<<(std::ostream& os, const BinaryParagraph& p) + { + os << "Package: " << p.name << "\n"; + os << "Version: " << p.version << "\n"; + if (!p.depends.empty()) + { + os << "Depends: " << p.depends.front(); + + auto b = p.depends.begin() + 1; + auto e = p.depends.end(); + for (; b != e; ++b) + { + os << ", " << *b; + } + + os << "\n"; + } + os << "Architecture: " << p.target_triplet << "\n"; + os << "Multi-Arch: same\n"; + if (!p.maintainer.empty()) + os << "Maintainer: " << p.maintainer << "\n"; + if (!p.description.empty()) + os << "Description: " << p.description << "\n"; + return os; + } +} diff --git a/toolsrc/src/SourceParagraph.cpp b/toolsrc/src/SourceParagraph.cpp new file mode 100644 index 000000000..7e3b0403e --- /dev/null +++ b/toolsrc/src/SourceParagraph.cpp @@ -0,0 +1,21 @@ +#include "SourceParagraph.h" +#include "vcpkglib_helpers.h" + +using namespace vcpkg::details; + +vcpkg::SourceParagraph::SourceParagraph() = default; + +vcpkg::SourceParagraph::SourceParagraph(const std::unordered_map<std::string, std::string>& fields) +{ + required_field(fields, name, "Source"); + required_field(fields, version, "Version"); + optional_field(fields, description, "Description"); + std::string deps; + optional_field(fields, deps, "Build-Depends"); + if (!deps.empty()) + { + depends.clear(); + parse_depends(deps, depends); + } + optional_field(fields, maintainer, "Maintainer"); +} diff --git a/toolsrc/src/StatusParagraph.cpp b/toolsrc/src/StatusParagraph.cpp new file mode 100644 index 000000000..09a3b4d45 --- /dev/null +++ b/toolsrc/src/StatusParagraph.cpp @@ -0,0 +1,87 @@ +#include "StatusParagraph.h" +#include "vcpkglib_helpers.h" + +using namespace vcpkg::details; + +namespace vcpkg +{ + StatusParagraph::StatusParagraph() : want(want_t::error), state(install_state_t::error) + { + } + + std::ostream& operator<<(std::ostream& os, const StatusParagraph& p) + { + os << p.package; + os << "Status: " << to_string(p.want) << " ok " << to_string(p.state) << "\n"; + return os; + } + + StatusParagraph::StatusParagraph(const std::unordered_map<std::string, std::string>& fields) + : package(fields) + { + std::string status_field; + required_field(fields, status_field, "Status"); + + auto b = status_field.begin(); + auto mark = b; + auto e = status_field.end(); + + // Todo: improve error handling + while (b != e && *b != ' ') + ++b; + + want = [](const std::string& text) + { + if (text == "unknown") + return want_t::unknown; + if (text == "install") + return want_t::install; + if (text == "hold") + return want_t::hold; + if (text == "deinstall") + return want_t::deinstall; + if (text == "purge") + return want_t::purge; + return want_t::error; + }(std::string(mark, b)); + + if (std::distance(b, e) < 4) + return; + b += 4; + + state = [](const std::string& text) + { + if (text == "not-installed") + return install_state_t::not_installed; + if (text == "installed") + return install_state_t::installed; + if (text == "half-installed") + return install_state_t::half_installed; + return install_state_t::error; + }(std::string(b, e)); + } + + std::string to_string(install_state_t f) + { + switch (f) + { + case install_state_t::half_installed: return "half-installed"; + case install_state_t::installed: return "installed"; + case install_state_t::not_installed: return "not-installed"; + default: return "error"; + } + } + + std::string to_string(want_t f) + { + switch (f) + { + case want_t::deinstall: return "deinstall"; + case want_t::hold: return "hold"; + case want_t::install: return "install"; + case want_t::purge: return "purge"; + case want_t::unknown: return "unknown"; + default: return "error"; + } + } +} diff --git a/toolsrc/src/StatusParagraphs.cpp b/toolsrc/src/StatusParagraphs.cpp new file mode 100644 index 000000000..463e3e3b8 --- /dev/null +++ b/toolsrc/src/StatusParagraphs.cpp @@ -0,0 +1,67 @@ +#include "StatusParagraphs.h" +#include <algorithm> +#include "vcpkg_Checks.h" + +namespace vcpkg +{ + StatusParagraphs::StatusParagraphs() = default; + + StatusParagraphs::StatusParagraphs(std::vector<std::unique_ptr<StatusParagraph>>&& ps) + : paragraphs(std::move(ps)) + { + }; + + StatusParagraphs::const_iterator StatusParagraphs::find(const std::string& name, const triplet& target_triplet) const + { + return std::find_if(begin(), end(), [&](const auto& pgh) + { + return pgh->package.name == name && pgh->package.target_triplet == target_triplet; + }); + } + + StatusParagraphs::iterator StatusParagraphs::find(const std::string& name, const triplet& target_triplet) + { + return std::find_if(begin(), end(), [&](const auto& pgh) + { + return pgh->package.name == name && pgh->package.target_triplet == target_triplet; + }); + } + + StatusParagraphs::iterator StatusParagraphs::find_installed(const std::string& name, const triplet& target_triplet) + { + auto it = find(name, target_triplet); + if (it != end() && (*it)->want == want_t::install) + { + return it; + } + + return end(); + } + + StatusParagraphs::iterator StatusParagraphs::insert(std::unique_ptr<StatusParagraph> pgh) + { + Checks::check_throw(pgh != nullptr, "Inserted null paragraph"); + auto ptr = find(pgh->package.name, pgh->package.target_triplet); + if (ptr == end()) + { + paragraphs.push_back(std::move(pgh)); + return paragraphs.rbegin(); + } + else + { + // consume data from provided pgh. + **ptr = std::move(*pgh); + return ptr; + } + } + + std::ostream& vcpkg::operator<<(std::ostream& os, const StatusParagraphs& l) + { + for (auto& pgh : l.paragraphs) + { + os << *pgh; + os << "\n"; + } + return os; + } +} diff --git a/toolsrc/src/commands_help.cpp b/toolsrc/src/commands_help.cpp new file mode 100644 index 000000000..4e1ae9c49 --- /dev/null +++ b/toolsrc/src/commands_help.cpp @@ -0,0 +1,55 @@ +#include "vcpkg_Commands.h" +#include "vcpkg.h" +#include "vcpkg_System.h" + +namespace vcpkg +{ + void version_command(const vcpkg_cmd_arguments& args) + { + args.check_max_args(0); + System::println("Vcpkg package management program version %s\n" + "\n" + "Vcpkg is provided \"as-is\" without warranty of any kind, express or implied.\n" + "All rights reserved.", vcpkg::version() + ); + exit(EXIT_SUCCESS); + } + + void help_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + args.check_max_args(1); + if (args.command_arguments.empty()) + { + print_usage(); + exit(EXIT_SUCCESS); + } + const auto& topic = args.command_arguments[0]; + if (topic == "triplet") + { + help_topic_valid_triplet(paths); + } + else + { + System::println(System::color::error, "Error: unknown topic %s", topic); + print_usage(); + exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + } + + void contact_command(const vcpkg_cmd_arguments& /*args*/) + { + System::println("Send an email to vcpkg@microsoft.com with any feedback."); + exit(EXIT_SUCCESS); + } + + void help_topic_valid_triplet(const vcpkg_paths& paths) + { + System::println("Available architecture triplets:"); + auto it = fs::directory_iterator(paths.triplets); + for (; it != fs::directory_iterator(); ++it) + { + System::println(" %s", it->path().stem().filename().string()); + } + } +} diff --git a/toolsrc/src/commands_installation.cpp b/toolsrc/src/commands_installation.cpp new file mode 100644 index 000000000..f6a774b8d --- /dev/null +++ b/toolsrc/src/commands_installation.cpp @@ -0,0 +1,219 @@ +#include "vcpkg_Commands.h" +#include "vcpkg.h" +#include <iostream> +#include <fstream> +#include <iomanip> +#include "vcpkg_Environment.h" +#include "metrics.h" +#include "vcpkg_Files.h" +#include "post_build_lint.h" +#include "vcpkg_System.h" + +namespace vcpkg +{ + static void create_binary_control_file(const vcpkg_paths& paths, const fs::path& port_dir, const triplet& target_triplet) + { + auto pghs = get_paragraphs(port_dir / "CONTROL"); + Checks::check_exit(pghs.size() == 1, "Error: invalid control file"); + auto bpgh = BinaryParagraph(SourceParagraph(pghs[0]), target_triplet); + const fs::path binary_control_file = paths.packages / bpgh.dir() / "CONTROL"; + std::ofstream(binary_control_file) << bpgh; + } + + static void build_internal(const package_spec& spec, const vcpkg_paths& paths, const fs::path& port_dir) + { + const fs::path ports_cmake_script_path = paths.ports_cmake; + const std::wstring vs140comntools = System::wdupenv_str(L"VS140COMNTOOLS"); + + const std::wstring command = Strings::format(LR"("%s..\..\VC\vcvarsall.bat" %s && cmake -DCMD=BUILD -DPORT=%s -DTARGET_TRIPLET=%s "-DCURRENT_PORT_DIR=%s/." -P "%s")", + vs140comntools, + Strings::utf8_to_utf16(spec.target_triplet.architecture()), + Strings::utf8_to_utf16(spec.name), + Strings::utf8_to_utf16(spec.target_triplet.value), + port_dir.generic_wstring(), + ports_cmake_script_path.generic_wstring()); + + System::Stopwatch timer; + timer.start(); + int return_code = System::cmd_execute(command); + timer.stop(); + TrackMetric("buildtimeus-" + to_string(spec), timer.microseconds()); + + if (return_code != 0) + { + System::println(System::color::error, "Error: build command failed"); + TrackProperty("error", "build failed"); + TrackProperty("build_error", std::to_string(return_code)); + exit(EXIT_FAILURE); + } + + perform_all_checks(spec, paths); + + create_binary_control_file(paths, port_dir, spec.target_triplet); + + // const fs::path port_buildtrees_dir = paths.buildtrees / spec.name; + // delete_directory(port_buildtrees_dir); + } + + static void build_internal(const package_spec& spec, const vcpkg_paths& paths) + { + return build_internal(spec, paths, paths.ports / spec.name); + } + + void install_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + StatusParagraphs status_db = database_load_check(paths); + + std::vector<package_spec> specs = args.extract_package_specs_with_unmet_dependencies(paths, default_target_triplet, status_db); + Checks::check_exit(!specs.empty(), "Specs cannot be empty"); + std::string specs_string = to_string(specs[0]); + for (size_t i = 1; i < specs.size(); ++i) + { + specs_string.push_back(','); + specs_string.append(to_string(specs[i])); + } + TrackProperty("installplan", specs_string); + Environment::ensure_utilities_on_path(paths); + + for (const package_spec& spec : specs) + { + if (status_db.find_installed(spec.name, spec.target_triplet) != status_db.end()) + { + System::println(System::color::success, "Package %s is already installed", spec); + continue; + } + + fs::path package_path = find_available_package(paths, spec); + + expected<std::string> file_contents = Files::get_contents(package_path / "CONTROL"); + + try + { + if (file_contents.error_code()) + { + build_internal(spec, paths); + file_contents = Files::get_contents(package_path / "CONTROL"); + if (file_contents.error_code()) + { + file_contents.get_or_throw(); + } + } + + auto pghs = parse_paragraphs(file_contents.get_or_throw()); + Checks::check_throw(pghs.size() == 1, "multiple paragraphs in control file"); + install_package(paths, BinaryParagraph(pghs[0]), status_db); + System::println(System::color::success, "Package %s is installed", spec); + } + catch (const std::exception& e) + { + System::println(System::color::error, "Error: Could not install package %s: %s", spec, e.what()); + exit(EXIT_FAILURE); + } + } + + exit(EXIT_SUCCESS); + } + + void search_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + args.check_max_args(1); + + if (args.command_arguments.size() == 1) + { + System::println(System::color::warning, "Search strings are not yet implemented; showing full list of packages."); + } + + auto begin_it = fs::directory_iterator(paths.ports); + auto end_it = fs::directory_iterator(); + for (; begin_it != end_it; ++begin_it) + { + const auto& path = begin_it->path(); + + try + { + auto pghs = get_paragraphs(path / "CONTROL"); + if (pghs.empty()) + continue; + auto srcpgh = SourceParagraph(pghs[0]); + std::cout << std::left + << std::setw(20) << srcpgh.name << ' ' + << std::setw(16) << srcpgh.version << ' ' + << shorten_description(srcpgh.description) << '\n'; + } + catch (std::runtime_error const&) + { + } + } + + System::println("\nIf your library is not listed, please open an issue at:\n" + " https://github.com/Microsoft/vcpkg/issues"); + + exit(EXIT_SUCCESS); + } + + void cache_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + args.check_max_args(0); + + auto begin_it = fs::directory_iterator(paths.packages); + auto end_it = fs::directory_iterator(); + + if (begin_it == end_it) + { + System::println("No packages are cached."); + exit(EXIT_SUCCESS); + } + + for (; begin_it != end_it; ++begin_it) + { + const auto& path = begin_it->path(); + + auto file_contents = Files::get_contents(path / "CONTROL"); + if (auto text = file_contents.get()) + { + auto pghs = parse_paragraphs(*text); + if (pghs.size() != 1) + continue; + + auto src = BinaryParagraph(pghs[0]); + System::println(src.displayname().c_str()); + } + } + + exit(EXIT_SUCCESS); + } + + void build_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + std::vector<package_spec> specs = args.parse_all_arguments_as_package_specs(default_target_triplet); + Environment::ensure_utilities_on_path(paths); + for (const package_spec& spec : specs) + { + build_internal(spec, paths); + } + exit(EXIT_SUCCESS); + } + + void build_external_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + if (args.command_arguments.size() != 2) + { + System::println(System::color::error, "Error: buildexternal requires the package name and the directory containing the CONTROL file"); + print_example(R"(buildexternal mylib C:\path\to\mylib\)"); + exit(EXIT_FAILURE); + } + + expected<package_spec> current_spec = vcpkg::parse(args.command_arguments[0], default_target_triplet); + if (auto spec = current_spec.get()) + { + Environment::ensure_utilities_on_path(paths); + const fs::path port_dir = args.command_arguments.at(1); + build_internal(*spec, paths, port_dir); + exit(EXIT_SUCCESS); + } + + System::println(System::color::error, "Error: %s: %s", current_spec.error_code().message(), args.command_arguments[0]); + print_example(Strings::format("%s zlib:x64-windows", args.command).c_str()); + exit(EXIT_FAILURE); + } +} diff --git a/toolsrc/src/commands_integration.cpp b/toolsrc/src/commands_integration.cpp new file mode 100644 index 000000000..e1b63038a --- /dev/null +++ b/toolsrc/src/commands_integration.cpp @@ -0,0 +1,297 @@ +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <shellapi.h> +#include "vcpkg_Commands.h" +#include "vcpkg.h" +#include <fstream> +#include <iostream> +#include <regex> +#include "vcpkg_Environment.h" +#include "vcpkg_Checks.h" +#include "vcpkg_System.h" + +namespace vcpkg +{ + static const fs::path old_system_wide_targets_file = "C:/Program Files (x86)/MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.nuget.targets"; + static const fs::path system_wide_targets_file = "C:/Program Files (x86)/MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.system.targets"; + + static std::string create_appdata_targets_shortcut(const std::string& target_path) noexcept + { + return Strings::format(R"###( +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Condition="Exists('%s') and '$(VCPkgLocalAppDataDisabled)' == ''" Project="%s" /> +</Project> +)###", target_path, target_path); + } + + static std::string create_system_targets_shortcut() noexcept + { + return R"###( +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <VCLibPackagePath Condition="'$(VCLibPackagePath)' == ''">$(LOCALAPPDATA)\vcpkg\vcpkg.user</VCLibPackagePath> + </PropertyGroup> + <Import Condition="'$(VCLibPackagePath)' != '' and Exists('$(VCLibPackagePath).props')" Project="$(VCLibPackagePath).props" /> +</Project> +)###"; + } + + static std::string create_nuget_targets_file(const fs::path& msbuild_vcpkg_targets_file) noexcept + { + const std::string as_string = msbuild_vcpkg_targets_file.string(); + + return Strings::format(R"###( +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="%s" Condition="Exists('%s')" /> + <Target Name="CheckValidPlatform" BeforeTargets="Build"> + <Error Text="Unsupported architecture combination. Remove the 'vcpkg' nuget package." Condition="'$(VCPkgEnabled)' != 'true' and '$(VCPkgDisableError)' == ''"/> + </Target> +</Project> +)###", as_string, as_string); + } + + static std::string create_nuget_props_file() noexcept + { + return R"###( +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup> + <VCPkgLocalAppDataDisabled>true</VCPkgLocalAppDataDisabled> + </PropertyGroup> +</Project> +)###"; + } + + static std::string get_nuget_id(const fs::path& vcpkg_root_dir) + { + std::string dir_id = vcpkg_root_dir.generic_string(); + std::replace(dir_id.begin(), dir_id.end(), '/', '.'); + dir_id.erase(1, 1); // Erasing the ":" + + // NuGet id cannot have invalid characters. We will only use alphanumeric and dot. + dir_id.erase(std::remove_if(dir_id.begin(), dir_id.end(), [](char c) + { + return !isalnum(c) && (c != '.'); + }), dir_id.end()); + + const std::string nuget_id = "vcpkg." + dir_id; + return nuget_id; + } + + static std::string create_nuspec_file(const fs::path& vcpkg_root_dir, const std::string& nuget_id, const std::string& nupkg_version) + { + const std::string nuspec_file_content_template = R"( +<package> + <metadata> + <id>@NUGET_ID@</id> + <version>@VERSION@</version> + <authors>cpp-packages</authors> + <description> + This package imports all libraries currently installed in @VCPKG_DIR@. This package does not contain any libraries and instead refers to the folder directly (like a symlink). + </description> + </metadata> + <files> + <file src="vcpkg.nuget.props" target="build\native\@NUGET_ID@.props" /> + <file src="vcpkg.nuget.targets" target="build\native\@NUGET_ID@.targets" /> + </files> +</package> +)"; + + std::string nuspec_file_content = std::regex_replace(nuspec_file_content_template, std::regex("@NUGET_ID@"), nuget_id); + nuspec_file_content = std::regex_replace(nuspec_file_content, std::regex("@VCPKG_DIR@"), vcpkg_root_dir.string()); + nuspec_file_content = std::regex_replace(nuspec_file_content, std::regex("@VERSION@"), nupkg_version); + return nuspec_file_content; + } + + enum class elevation_prompt_user_choice + { + yes, + no + }; + + static elevation_prompt_user_choice elevated_cmd_execute(const std::string& param) + { + SHELLEXECUTEINFO shExInfo = {0}; + shExInfo.cbSize = sizeof(shExInfo); + shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + shExInfo.hwnd = nullptr; + shExInfo.lpVerb = "runas"; + shExInfo.lpFile = "cmd"; // Application to start + + shExInfo.lpParameters = param.c_str(); // Additional parameters + shExInfo.lpDirectory = nullptr; + shExInfo.nShow = SW_HIDE; + shExInfo.hInstApp = nullptr; + + if (!ShellExecuteExA(&shExInfo)) + { + return elevation_prompt_user_choice::no; + } + if (shExInfo.hProcess == nullptr) + { + return elevation_prompt_user_choice::no; + } + WaitForSingleObject(shExInfo.hProcess, INFINITE); + CloseHandle(shExInfo.hProcess); + return elevation_prompt_user_choice::yes; + } + + static fs::path get_appdata_targets_path() + { + return fs::path(System::wdupenv_str(L"LOCALAPPDATA")) / "vcpkg" / "vcpkg.user.targets"; + } + + static void integrate_install(const vcpkg_paths& paths) + { + // TODO: This block of code should eventually be removed + { + if (fs::exists(old_system_wide_targets_file)) + { + const std::string param = Strings::format(R"(/c DEL "%s" /Q > nul)", old_system_wide_targets_file.string()); + elevation_prompt_user_choice user_choice = elevated_cmd_execute(param); + switch (user_choice) + { + case elevation_prompt_user_choice::yes: + break; + case elevation_prompt_user_choice::no: + System::println(System::color::warning, "Warning: Previous integration file was not removed"); + exit(EXIT_FAILURE); + default: + Checks::unreachable(); + } + } + } + + const fs::path tmp_dir = paths.buildsystems / "tmp"; + fs::create_directory(paths.buildsystems); + fs::create_directory(tmp_dir); + + if (!fs::exists(system_wide_targets_file)) + { + const fs::path sys_src_path = tmp_dir / "vcpkg.system.targets"; + std::ofstream(sys_src_path) << create_system_targets_shortcut(); + + const std::string param = Strings::format(R"(/c COPY "%s" "%s" /Y > nul)", sys_src_path.string(), system_wide_targets_file.string()); + elevation_prompt_user_choice user_choice = elevated_cmd_execute(param); + switch (user_choice) + { + case elevation_prompt_user_choice::yes: + break; + case elevation_prompt_user_choice::no: + System::println(System::color::warning, "Warning: integration was not applied"); + exit(EXIT_FAILURE); + default: + Checks::unreachable(); + } + } + + const fs::path appdata_src_path = tmp_dir / "vcpkg.user.targets"; + std::ofstream(appdata_src_path) << create_appdata_targets_shortcut(paths.buildsystems_msbuild_targets.string()); + auto appdata_dst_path = get_appdata_targets_path(); + + if (!fs::copy_file(appdata_src_path, appdata_dst_path, fs::copy_options::overwrite_existing)) + { + System::println(System::color::error, "Error: Failed to copy file: %s -> %s", appdata_src_path.string(), appdata_dst_path.string()); + exit(EXIT_FAILURE); + } + System::println(System::color::success, "Applied user-wide integration for this vcpkg root."); + System::println("\n" + "All C++ projects can now #include any installed libraries.\n" + "Linking will be handled automatically.\n" + "Installing new libraries will make them instantly available."); + + exit(EXIT_SUCCESS); + } + + static void integrate_remove() + { + auto path = get_appdata_targets_path(); + if (!fs::exists(path)) + { + System::println(System::color::success, "User-wide integration is not installed"); + exit(EXIT_SUCCESS); + } + + const std::wstring cmd_line = Strings::format(LR"(DEL "%s")", get_appdata_targets_path().native()); + const int exit_code = System::cmd_execute(cmd_line); + if (exit_code) + { + System::println(System::color::error, "Error: Unable to remove user-wide integration: %d", exit_code); + exit(exit_code); + } + System::println(System::color::success, "User-wide integration was removed"); + exit(EXIT_SUCCESS); + } + + static void integrate_project(const vcpkg_paths& paths) + { + Environment::ensure_nuget_on_path(paths); + + const fs::path& buildsystems_dir = paths.buildsystems; + const fs::path tmp_dir = buildsystems_dir / "tmp"; + fs::create_directory(buildsystems_dir); + fs::create_directory(tmp_dir); + + const fs::path targets_file_path = tmp_dir / "vcpkg.nuget.targets"; + const fs::path props_file_path = tmp_dir / "vcpkg.nuget.props"; + const fs::path nuspec_file_path = tmp_dir / "vcpkg.nuget.nuspec"; + const std::string nuget_id = get_nuget_id(paths.root); + const std::string nupkg_version = "1.0.0"; + + std::ofstream(targets_file_path) << create_nuget_targets_file(paths.buildsystems_msbuild_targets); + std::ofstream(props_file_path) << create_nuget_props_file(); + std::ofstream(nuspec_file_path) << create_nuspec_file(paths.root, nuget_id, nupkg_version); + + // Using all forward slashes for the command line + const std::wstring cmd_line = Strings::format(LR"(nuget.exe pack -OutputDirectory "%s" "%s" > nul)", buildsystems_dir.native(), nuspec_file_path.native()); + + const int exit_code = System::cmd_execute(cmd_line); + + const fs::path nuget_package = buildsystems_dir / Strings::format("%s.%s.nupkg", nuget_id, nupkg_version); + if (exit_code != 0 || !fs::exists(nuget_package)) + { + System::println(System::color::error, "Error: NuGet package creation failed"); + exit(EXIT_FAILURE); + } + + System::println(System::color::success, "Created nupkg: %s", nuget_package.string()); + + System::println(R"( +With a project open, go to Tools->NuGet Package Manager->Package Manager Console and paste: + Install-Package %s -Source "%s" +)", nuget_id, buildsystems_dir.generic_string()); + + exit(EXIT_SUCCESS); + } + + const char* const INTEGRATE_COMMAND_HELPSTRING = + " vcpkg integrate install Make installed packages available user-wide. Requires admin privileges on first use\n" + " vcpkg integrate remove Remove user-wide integration\n" + " vcpkg integrate project Generate a referencing nuget package for individual VS project use\n"; + + void integrate_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + if (args.command_arguments.size() != 1) + { + std::cout << "Commands:\n" << + INTEGRATE_COMMAND_HELPSTRING << + "\n"; + exit(EXIT_FAILURE); + } + + if (args.command_arguments[0] == "install") + { + return integrate_install(paths); + } + if (args.command_arguments[0] == "remove") + { + return integrate_remove(); + } + if (args.command_arguments[0] == "project") + { + return integrate_project(paths); + } + + System::println(System::color::error, "Unknown parameter %s for integrate", args.command_arguments[0]); + exit(EXIT_FAILURE); + } +} diff --git a/toolsrc/src/commands_other.cpp b/toolsrc/src/commands_other.cpp new file mode 100644 index 000000000..c7dcc2586 --- /dev/null +++ b/toolsrc/src/commands_other.cpp @@ -0,0 +1,293 @@ +#include "vcpkg_Commands.h" +#include <iostream> +#include <unordered_set> +#include "vcpkg_Environment.h" +#include "vcpkg.h" +#include "vcpkg_System.h" +#include "vcpkg_Files.h" + +namespace vcpkg +{ + void print_usage() + { + std::cout << "Commands:\n" + " vcpkg search [pat] Search for packages available to be built\n" + " vcpkg install <pkg> Install a package\n" + " vcpkg remove <pkg> Uninstall a package. \n" + " vcpkg remove --purge <pkg> Uninstall and delete a package. \n" + " vcpkg list List installed packages\n" + " vcpkg update Display list of packages for updating\n" + "\n" + << INTEGRATE_COMMAND_HELPSTRING << + "\n" + " vcpkg edit <pkg> Open up a port for editing (uses %EDITOR%, default 'code')\n" + " vcpkg import <pkg> Import a pre-built library\n" + " vcpkg create <pkg> <url>\n" + " [archivename] Create a new package\n" + " vcpkg owns <pat> Search for files in installed packages\n" + " vcpkg cache List cached compiled packages\n" + " vcpkg version Display version information\n" + " vcpkg contact Display contact information to send feedback\n" + "\n" + //"internal commands:\n" + //" --check-build-deps <controlfile>\n" + //" --create-binary-control <controlfile>\n" + //"\n" + "Options:\n" + " --triplet <t> Specify the target architecture triplet.\n" + " (default: x86-windows, see 'vcpkg help triplet')\n" + "\n" + " --vcpkg-root <path> Specify the vcpkg root directory\n" + " (default: %VCPKG_ROOT%)\n" + "\n" + "For more help (including examples) see the accompanying README.md." + "\n"; + } + + void print_example(const char* command_and_arguments) + { + std::cout << + "Example:\n" + " vcpkg " << command_and_arguments << "\n"; + } + + void update_command(const vcpkg_cmd_arguments& /*args*/, const vcpkg_paths& paths) + { + auto status_db = database_load_check(paths); + + std::unordered_map<std::string, std::string> src_names_to_versions; + + auto begin_it = fs::directory_iterator(paths.ports); + auto end_it = fs::directory_iterator(); + for (; begin_it != end_it; ++begin_it) + { + const auto& path = begin_it->path(); + try + { + auto pghs = get_paragraphs(path / "CONTROL"); + if (pghs.empty()) + continue; + auto srcpgh = SourceParagraph(pghs[0]); + src_names_to_versions.emplace(srcpgh.name, srcpgh.version); + } + catch (std::runtime_error const&) + { + } + } + + std::string packages_list; + + std::vector<std::string> packages_output; + for (auto&& pgh : database_load_check(paths)) + { + if (pgh->state == install_state_t::not_installed && pgh->want == want_t::purge) + continue; + auto it = src_names_to_versions.find(pgh->package.name); + if (it == src_names_to_versions.end()) + { + // Package was not installed from portfile + continue; + } + if (it->second != pgh->package.version) + { + packages_output.push_back(Strings::format("%-27s %s -> %s", + pgh->package.displayname(), + pgh->package.version, + it->second)); + packages_list.append(" " + pgh->package.displayname()); + } + } + std::sort(packages_output.begin(), packages_output.end()); + if (packages_output.empty()) + { + System::println("No packages need updating."); + } + else + { + System::println("The following packages differ from their port versions:"); + for (auto&& package : packages_output) + { + System::println(" %s", package.c_str()); + } + System::println("\nTo update these packages, run\n vcpkg remove --purge <pkgs>...\n vcpkg install <pkgs>..."); + } + + auto version_file = Files::get_contents(paths.root / "toolsrc" / "VERSION.txt"); + if (auto version_contents = version_file.get()) + { + int maj1, min1, rev1; + auto num1 = sscanf_s(version_contents->c_str(), "\"%d.%d.%d\"", &maj1, &min1, &rev1); + + int maj2, min2, rev2; + auto num2 = sscanf_s(version().c_str(), "%d.%d.%d-", &maj2, &min2, &rev2); + + if (num1 == 3 && num2 == 3) + { + if (maj1 != maj2 || min1 != min2 || rev1 != rev2) + { + System::println("Different source is available for vcpkg (%d.%d.%d -> %d.%d.%d). Use scripts\\bootstrap.ps1 to update.", + maj2, min2, rev2, + maj1, min1, rev1); + } + } + } + + exit(EXIT_SUCCESS); + } + + void edit_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + static auto example = "edit zlib"; + args.check_max_args(1, example); + package_spec spec = args.parse_all_arguments_as_package_specs(default_target_triplet, example).at(0); + + // Find editor + std::wstring env_EDITOR = System::wdupenv_str(L"EDITOR"); + if (env_EDITOR.empty()) + env_EDITOR = LR"(C:\Program Files (x86)\Microsoft VS Code\Code.exe)"; + + auto portpath = paths.ports / spec.name; + std::wstring cmdLine = Strings::format(LR"("%s" "%s" "%s")", env_EDITOR, portpath.native(), (portpath / "portfile.cmake").native()); + exit(System::cmd_execute(cmdLine)); + } + + void create_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + args.check_max_args(3); + package_spec spec = args.parse_all_arguments_as_package_specs(default_target_triplet).at(0); + if (args.command_arguments.size() < 2) + { + System::println(System::color::error, "Error: create requires the archive's URL as the second argument."); + print_usage(); + exit(EXIT_FAILURE); + } + Environment::ensure_utilities_on_path(paths); + + // Space OR define the FILENAME with proper spacing + std::wstring custom_filename = L" "; + if (args.command_arguments.size() >= 3) + { + custom_filename = Strings::format(L" -DFILENAME=%s ", Strings::utf8_to_utf16(args.command_arguments.at(2))); + } + + const std::wstring cmdline = Strings::format(LR"(cmake -DCMD=SCAFFOLD -DPORT=%s -DTARGET_TRIPLET=%s -DURL=%s%s-P "%s")", + Strings::utf8_to_utf16(spec.name), + Strings::utf8_to_utf16(spec.target_triplet.value), + Strings::utf8_to_utf16(args.command_arguments.at(1)), + custom_filename, + paths.ports_cmake.generic_wstring()); + + exit(System::cmd_execute(cmdline)); + } + + void list_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + args.check_max_args(0); + + std::vector<std::string> packages_output; + for (auto&& pgh : database_load_check(paths)) + { + if (pgh->state == install_state_t::not_installed && pgh->want == want_t::purge) + continue; + packages_output.push_back(Strings::format("%-27s %-16s %s", + pgh->package.displayname(), + pgh->package.version, + shorten_description(pgh->package.description))); + } + std::sort(packages_output.begin(), packages_output.end()); + for (auto&& package : packages_output) + { + System::println(package.c_str()); + } + if (packages_output.empty()) + { + System::println("No packages are installed. Did you mean `search`?"); + } + exit(EXIT_SUCCESS); + } + + void import_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + if (args.command_arguments.size() != 3) + { + System::println(System::color::error, "Error: %s requires 3 parameters", args.command); + print_example(Strings::format(R"(%s C:\path\to\CONTROLfile C:\path\to\includedir C:\path\to\projectdir)", args.command).c_str()); + exit(EXIT_FAILURE); + } + + const fs::path control_file_path(args.command_arguments[0]); + const fs::path include_directory(args.command_arguments[1]); + const fs::path project_directory(args.command_arguments[2]); + + auto pghs = get_paragraphs(control_file_path); + Checks::check_throw(pghs.size() == 1, "Invalid control file for package"); + + StatusParagraph spgh; + spgh.package = BinaryParagraph(pghs[0]); + auto& control_file_data = spgh.package; + + vcpkg::binary_import(paths, include_directory, project_directory, control_file_data); + exit(EXIT_SUCCESS); + } + + void owns_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + { + args.check_max_args(1); + if (args.command_arguments.size() == 0) + { + System::println(System::color::error, "Error: owns requires a pattern to search for as the first argument."); + std::cout << + "example:\n" + " vcpkg owns .dll\n"; + exit(EXIT_FAILURE); + } + StatusParagraphs status_db = database_load_check(paths); + search_file(paths, args.command_arguments[0], status_db); + exit(EXIT_SUCCESS); + } + + void internal_test_command(const vcpkg_cmd_arguments& /*args*/, const vcpkg_paths& /*paths*/) + { + // auto data = FormatEventData("test"); + // Track(data); + exit(EXIT_SUCCESS); + } + + const std::vector<package_name_and_function<command_type_a>>& get_available_commands_type_a() + { + static std::vector<package_name_and_function<command_type_a>> t = { + {"install", install_command}, + {"remove", remove_command}, + {"build", build_command}, + {"edit", edit_command}, + {"create", create_command}, + {"build_external", build_external_command} + }; + return t; + } + + const std::vector<package_name_and_function<command_type_b>>& get_available_commands_type_b() + { + static std::vector<package_name_and_function<command_type_b>> t = { + {"help", help_command}, + {"search", search_command}, + {"list", list_command}, + {"integrate", integrate_command}, + {"owns", owns_command}, + {"update", update_command}, + {"import", import_command}, + {"cache", cache_command}, + {"internal_test", internal_test_command}, + }; + return t; + } + + const std::vector<package_name_and_function<command_type_c>>& get_available_commands_type_c() + { + static std::vector<package_name_and_function<command_type_c>> t = { + {"version", &version_command}, + {"contact", &contact_command} + }; + return t; + } +} diff --git a/toolsrc/src/commands_remove.cpp b/toolsrc/src/commands_remove.cpp new file mode 100644 index 000000000..f5315ccb1 --- /dev/null +++ b/toolsrc/src/commands_remove.cpp @@ -0,0 +1,43 @@ +#include "vcpkg_Commands.h" +#include "vcpkg.h" +#include "vcpkg_System.h" + +namespace vcpkg +{ + static const std::string OPTION_PURGE = "--purge"; + + static void delete_directory(const fs::path& directory) + { + std::error_code ec; + fs::remove_all(directory, ec); + if (!ec) + { + System::println(System::color::success, "Cleaned up %s", directory.string()); + } + if (fs::exists(directory)) + { + System::println(System::color::warning, "Some files in %s were unable to be removed. Close any editors operating in this directory and retry.", directory.string()); + } + } + + void remove_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments({OPTION_PURGE}); + auto status_db = database_load_check(paths); + + std::vector<package_spec> specs = args.parse_all_arguments_as_package_specs(default_target_triplet); + bool alsoRemoveFolderFromPackages = options.find(OPTION_PURGE) != options.end(); + + for (const package_spec& spec : specs) + { + deinstall_package(paths, spec, status_db); + + if (alsoRemoveFolderFromPackages) + { + const fs::path spec_package_dir = paths.packages / spec.dir(); + delete_directory(spec_package_dir); + } + } + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/lib.cpp b/toolsrc/src/lib.cpp new file mode 100644 index 000000000..2a3b95182 --- /dev/null +++ b/toolsrc/src/lib.cpp @@ -0,0 +1,524 @@ +#include "vcpkg.h" +#include <iostream> +#include <iomanip> +#include <fstream> +#include <functional> +#include <string> +#include <unordered_map> +#include <memory> +#include <filesystem> +#include <vector> +#include <cassert> +#include "vcpkg_Files.h" +#include "vcpkg_System.h" + +using namespace vcpkg; + +bool vcpkg::g_do_dry_run = false; + +namespace +{ + template <class M, class K, class V> + auto find_or_default(const M& map, const K& key, const V& val) + { + auto it = map.find(key); + if (it == map.end()) + return decltype(it->second)(val); + else + return it->second; + } +} + +namespace +{ + std::fstream open_status_file(const vcpkg_paths& paths, std::ios_base::openmode mode = std::ios_base::app | std::ios_base::in | std::ios_base::out | std::ios_base::binary) + { + return std::fstream(paths.vcpkg_dir_status_file, mode); + } +} + +static StatusParagraphs load_current_database(const fs::path& vcpkg_dir_status_file, const fs::path& vcpkg_dir_status_file_old) +{ + if (!fs::exists(vcpkg_dir_status_file)) + { + if (!fs::exists(vcpkg_dir_status_file_old)) + { + // no status file, use empty db + return StatusParagraphs(); + } + + fs::rename(vcpkg_dir_status_file_old, vcpkg_dir_status_file); + } + + auto text = Files::get_contents(vcpkg_dir_status_file).get_or_throw(); + auto pghs = parse_paragraphs(text); + + std::vector<std::unique_ptr<StatusParagraph>> status_pghs; + for (auto&& p : pghs) + { + status_pghs.push_back(std::make_unique<StatusParagraph>(p)); + } + + return StatusParagraphs(std::move(status_pghs)); +} + +StatusParagraphs vcpkg::database_load_check(const vcpkg_paths& paths) +{ + auto updates_dir = paths.vcpkg_dir_updates; + + std::error_code ec; + fs::create_directory(paths.installed, ec); + fs::create_directory(paths.vcpkg_dir, ec); + fs::create_directory(paths.vcpkg_dir_info, ec); + fs::create_directory(updates_dir, ec); + + const fs::path& status_file = paths.vcpkg_dir_status_file; + const fs::path status_file_old = status_file.parent_path() / "status-old"; + const fs::path status_file_new = status_file.parent_path() / "status-new"; + + StatusParagraphs current_status_db = load_current_database(status_file, status_file_old); + + auto b = fs::directory_iterator(updates_dir); + auto e = fs::directory_iterator(); + if (b == e) + { + // updates directory is empty, control file is up-to-date. + return current_status_db; + } + + for (; b != e; ++b) + { + if (!fs::is_regular_file(b->status())) + continue; + if (b->path().filename() == "incomplete") + continue; + + auto text = Files::get_contents(b->path()).get_or_throw(); + auto pghs = parse_paragraphs(text); + for (auto&& p : pghs) + { + current_status_db.insert(std::make_unique<StatusParagraph>(p)); + } + } + + std::fstream(status_file_new, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc) << current_status_db; + + if (fs::exists(status_file_old)) + fs::remove(status_file_old); + if (fs::exists(status_file)) + fs::rename(status_file, status_file_old); + fs::rename(status_file_new, status_file); + fs::remove(status_file_old); + + b = fs::directory_iterator(updates_dir); + for (; b != e; ++b) + { + if (!fs::is_regular_file(b->status())) + continue; + fs::remove(b->path()); + } + + return current_status_db; +} + +static fs::path listfile_path(const vcpkg_paths& paths, const BinaryParagraph& pgh) +{ + return paths.vcpkg_dir_info / (pgh.fullstem() + ".list"); +} + +static std::string get_fullpkgname_from_listfile(const fs::path& path) +{ + auto ret = path.stem().generic_u8string(); + std::replace(ret.begin(), ret.end(), '_', ':'); + return ret; +} + +fs::path vcpkg::control_file_for_package(const fs::path& package) +{ + return package / "CONTROL"; +} + +fs::path vcpkg::find_available_package(const vcpkg_paths& paths, const package_spec& spec) +{ + return paths.packages / Strings::format("%s_%s", spec.name, spec.target_triplet); +} + +fs::path vcpkg::find_available_port_file(const vcpkg_paths& paths, const package_spec& spec) +{ + return paths.ports / spec.name; +} + +static fs::path prefix_path_for_package(const vcpkg_paths& paths, const BinaryParagraph& pgh) +{ + return find_available_package(paths, {pgh.name, pgh.target_triplet}); +} + +static void write_update(const vcpkg_paths& paths, const StatusParagraph& p) +{ + static int update_id = 0; + auto my_update_id = update_id++; + auto tmp_update_filename = paths.vcpkg_dir_updates / "incomplete"; + auto update_filename = paths.vcpkg_dir_updates / std::to_string(my_update_id); + std::fstream fs(tmp_update_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + fs << p; + fs.close(); + fs::rename(tmp_update_filename, update_filename); +} + +static void install_and_write_listfile(const vcpkg_paths& paths, const BinaryParagraph& bpgh) +{ + std::fstream listfile(listfile_path(paths, bpgh), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + + auto package_prefix_path = prefix_path_for_package(paths, bpgh); + auto prefix_length = package_prefix_path.native().size(); + + std::error_code ec; + fs::create_directory(paths.installed / bpgh.target_triplet.value, ec); + listfile << bpgh.target_triplet << "\n"; + + for (auto it = fs::recursive_directory_iterator(package_prefix_path); it != fs::recursive_directory_iterator(); ++it) + { + const auto& filename = it->path().filename(); + if (fs::is_regular_file(it->status()) && (filename == "CONTROL" || filename == "control")) + { + // Do not copy the control file + continue; + } + + auto suffix = it->path().generic_u8string().substr(prefix_length + 1); + auto target = paths.installed / bpgh.target_triplet.value / suffix; + + auto status = it->status(ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + continue; + } + if (fs::is_directory(status)) + { + fs::create_directory(target, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + } + + listfile << bpgh.target_triplet << "/" << suffix << "\n"; + } + else if (fs::is_regular_file(status)) + { + fs::copy_file(*it, target, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + } + listfile << bpgh.target_triplet << "/" << suffix << "\n"; + } + else if (!fs::status_known(status)) + { + std::cout << "unknown status: " << *it << "\n"; + } + else + std::cout << "warning: file does not exist: " << *it << "\n"; + } + + listfile.close(); +} + +// TODO: Refactoring between this function and install_package +std::vector<std::string> vcpkg::get_unmet_package_dependencies(const vcpkg_paths& paths, const package_spec& spec, const StatusParagraphs& status_db) +{ + std::vector<std::unordered_map<std::string, std::string>> pghs; + const fs::path packages_dir_control_file_path = find_available_package(paths, spec) / "CONTROL"; + + if (fs::exists(packages_dir_control_file_path)) + { + try + { + pghs = get_paragraphs(packages_dir_control_file_path); + } + catch (std::runtime_error) + { + // ?? + } + + Checks::check_throw(pghs.size() == 1, "Invalid control file for package"); + return BinaryParagraph(pghs[0]).depends; + } + + const fs::path ports_dir_control_file_path = find_available_port_file(paths, spec) / "CONTROL"; + try + { + pghs = get_paragraphs(ports_dir_control_file_path); + } + catch (std::runtime_error) + { + // ?? + } + + Checks::check_exit(pghs.size() == 1, "Invalid control file for package %s", spec); + return SourceParagraph(pghs[0]).depends; +} + +void vcpkg::install_package(const vcpkg_paths& paths, const BinaryParagraph& binary_paragraph, StatusParagraphs& status_db) +{ + StatusParagraph spgh; + spgh.package = binary_paragraph; + spgh.want = want_t::install; + spgh.state = install_state_t::half_installed; + for (const std::string& dependency : spgh.package.depends) + { + if (status_db.find_installed(dependency, spgh.package.target_triplet) == status_db.end()) + { + std::abort(); + } + } + write_update(paths, spgh); + status_db.insert(std::make_unique<StatusParagraph>(spgh)); + + install_and_write_listfile(paths, spgh.package); + + spgh.state = install_state_t::installed; + write_update(paths, spgh); + status_db.insert(std::make_unique<StatusParagraph>(spgh)); +} + +enum class deinstall_plan +{ + not_installed, + dependencies_not_satisfied, + should_deinstall +}; + +static deinstall_plan deinstall_package_plan( + const StatusParagraphs::iterator package_it, + const StatusParagraphs& status_db, + std::vector<const StatusParagraph*>& dependencies_out) +{ + dependencies_out.clear(); + + if (package_it == status_db.end() || (*package_it)->state == install_state_t::not_installed) + { + return deinstall_plan::not_installed; + } + + auto& pkg = (*package_it)->package; + + for (auto&& inst_pkg : status_db) + { + if (inst_pkg->want != want_t::install) + continue; + if (inst_pkg->package.target_triplet != pkg.target_triplet) + continue; + + const auto& deps = inst_pkg->package.depends; + + if (std::find(deps.begin(), deps.end(), pkg.name) != deps.end()) + { + dependencies_out.push_back(inst_pkg.get()); + } + } + + if (!dependencies_out.empty()) + return deinstall_plan::dependencies_not_satisfied; + + return deinstall_plan::should_deinstall; +} + +void vcpkg::deinstall_package(const vcpkg_paths& paths, const package_spec& spec, StatusParagraphs& status_db) +{ + auto package_it = status_db.find(spec.name, spec.target_triplet); + if (package_it == status_db.end()) + { + System::println(System::color::success, "Package %s is not installed", spec); + return; + } + + auto& pkg = **package_it; + + std::vector<const StatusParagraph*> deps; + auto plan = deinstall_package_plan(package_it, status_db, deps); + switch (plan) + { + case deinstall_plan::not_installed: + System::println(System::color::success, "Package %s is not installed", spec); + return; + case deinstall_plan::dependencies_not_satisfied: + System::println(System::color::error, "Error: Cannot remove package %s:", spec); + for (auto&& dep : deps) + { + System::println(" %s depends on %s", dep->package.displayname(), pkg.package.displayname()); + } + exit(EXIT_FAILURE); + case deinstall_plan::should_deinstall: + break; + default: + Checks::unreachable(); + } + + pkg.want = want_t::purge; + pkg.state = install_state_t::half_installed; + write_update(paths, pkg); + + std::fstream listfile(listfile_path(paths, pkg.package), std::ios_base::in | std::ios_base::binary); + if (listfile) + { + std::vector<fs::path> dirs_touched; + std::string suffix; + while (std::getline(listfile, suffix)) + { + if (!suffix.empty() && suffix.back() == '\r') + suffix.pop_back(); + + std::error_code ec; + + auto target = paths.installed / suffix; + + auto status = fs::status(target, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + continue; + } + + if (fs::is_directory(status)) + { + dirs_touched.push_back(target); + } + else if (fs::is_regular_file(status)) + { + fs::remove(target, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + } + } + else if (!fs::status_known(status)) + { + System::println(System::color::warning, "Warning: unknown status: %s", target.string()); + } + else + { + System::println(System::color::warning, "Warning: ???: %s", target.string()); + } + } + + auto b = dirs_touched.rbegin(); + auto e = dirs_touched.rend(); + for (; b != e; ++b) + { + if (fs::directory_iterator(*b) == fs::directory_iterator()) + { + std::error_code ec; + fs::remove(*b, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + } + } + } + + listfile.close(); + fs::remove(listfile_path(paths, pkg.package)); + } + + pkg.state = install_state_t::not_installed; + write_update(paths, pkg); + System::println(System::color::success, "Package %s was successfully removed", pkg.package.displayname()); +} + +void vcpkg::search_file(const vcpkg_paths& paths, const std::string& file_substr, const StatusParagraphs& status_db) +{ + std::string line; + + for (auto&& pgh : status_db) + { + if (pgh->state != install_state_t::installed) + continue; + + std::fstream listfile(listfile_path(paths, pgh->package)); + while (std::getline(listfile, line)) + { + if (line.empty()) + { + continue; + } + + if (line.find(file_substr) != std::string::npos) + { + System::println("%s: %s", pgh->package.displayname(), line); + } + } + } +} + +namespace +{ + struct Binaries + { + std::vector<fs::path> dlls; + std::vector<fs::path> libs; + }; + + Binaries detect_files_in_directory_ending_with(const fs::path& path) + { + Files::check_is_directory(path); + + Binaries binaries; + + for (auto it = fs::recursive_directory_iterator(path); it != fs::recursive_directory_iterator(); ++it) + { + fs::path file = *it; + // Skip if directory ????? + if (file.extension() == ".dll") + { + binaries.dlls.push_back(file); + } + else if (file.extension() == ".lib") + { + binaries.libs.push_back(file); + } + } + + return binaries; + } + + void copy_files_into_directory(const std::vector<fs::path>& files, const fs::path& destination_folder) + { + fs::create_directory(destination_folder); + + for (auto const& src_path : files) + { + fs::path dest_path = destination_folder / src_path.filename(); + fs::copy(src_path, dest_path, fs::copy_options::overwrite_existing); + } + } + + void place_library_files_in(const fs::path& include_directory, const fs::path& project_directory, const fs::path& destination_path) + { + Files::check_is_directory(include_directory); + Files::check_is_directory(project_directory); + Files::check_is_directory(destination_path); + Binaries debug_binaries = detect_files_in_directory_ending_with(project_directory / "Debug"); + Binaries release_binaries = detect_files_in_directory_ending_with(project_directory / "Release"); + + fs::path destination_include_directory = destination_path / "include"; + fs::copy(include_directory, destination_include_directory, fs::copy_options::recursive | fs::copy_options::overwrite_existing); + + copy_files_into_directory(release_binaries.dlls, destination_path / "bin"); + copy_files_into_directory(release_binaries.libs, destination_path / "lib"); + + fs::create_directory(destination_path / "debug"); + copy_files_into_directory(debug_binaries.dlls, destination_path / "debug" / "bin"); + copy_files_into_directory(debug_binaries.libs, destination_path / "debug" / "lib"); + } +} + +void vcpkg::binary_import(const vcpkg_paths& paths, const fs::path& include_directory, const fs::path& project_directory, const BinaryParagraph& control_file_data) +{ + fs::path library_destination_path = prefix_path_for_package(paths, control_file_data); + fs::create_directory(library_destination_path); + place_library_files_in(include_directory, project_directory, library_destination_path); + + fs::path control_file_path = library_destination_path / "CONTROL"; + std::ofstream(control_file_path) << control_file_data; +} diff --git a/toolsrc/src/main.cpp b/toolsrc/src/main.cpp new file mode 100644 index 000000000..bba1f3c63 --- /dev/null +++ b/toolsrc/src/main.cpp @@ -0,0 +1,245 @@ +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#include <iostream> +#include <fstream> +#include <memory> +#include <cassert> +#include "vcpkg.h" +#include "vcpkg_Commands.h" +#include "metrics.h" +#include <Shlobj.h> +#include "vcpkg_Files.h" +#include "vcpkg_System.h" + +using namespace vcpkg; + +bool g_debugging = false; + +void invalid_command(const std::string& cmd) +{ + System::println(System::color::error, "invalid command: %s", cmd); + print_usage(); + exit(EXIT_FAILURE); +} + +static void inner(const vcpkg_cmd_arguments& args) +{ + TrackProperty("command", args.command); + if (args.command.empty()) + { + print_usage(); + exit(EXIT_FAILURE); + } + + if (auto command_function = find_command(args.command, get_available_commands_type_c())) + { + return command_function(args); + } + + fs::path vcpkg_root_dir; + if (args.vcpkg_root_dir != nullptr) + { + vcpkg_root_dir = fs::absolute(Strings::utf8_to_utf16(*args.vcpkg_root_dir)); + } + else + { + auto vcpkg_root_dir_env = System::wdupenv_str(L"VCPKG_ROOT"); + + if (!vcpkg_root_dir_env.empty()) + vcpkg_root_dir = fs::absolute(vcpkg_root_dir_env); + else + vcpkg_root_dir = fs::absolute(System::get_exe_path_of_current_process()).parent_path(); + } + + const expected<vcpkg_paths> expected_paths = vcpkg_paths::create(vcpkg_root_dir); + Checks::check_exit(!expected_paths.error_code(), "Error: Invalid vcpkg root directory %s: %s", vcpkg_root_dir.string(), expected_paths.error_code().message()); + const vcpkg_paths paths = expected_paths.get_or_throw(); + int exit_code = _wchdir(paths.root.c_str()); + Checks::check_exit(exit_code == 0, "Changing the working dir failed"); + + if (auto command_function = find_command(args.command, get_available_commands_type_b())) + { + return command_function(args, paths); + } + + triplet default_target_triplet = triplet::X86_WINDOWS; + + if (args.target_triplet != nullptr) + { + const std::string& target_triplet = *args.target_triplet; + + auto it = fs::directory_iterator(paths.triplets); + for (; it != fs::directory_iterator(); ++it) + { + std::string triplet_file_name = it->path().stem().generic_u8string(); + if (target_triplet == triplet_file_name) // TODO: fuzzy compare + { + default_target_triplet = {triplet_file_name}; + break; + } + } + + if (it == fs::directory_iterator()) + { + System::println(System::color::error, "Error: invalid triplet: %s", target_triplet); + TrackProperty("error", "invalid triplet: " + target_triplet); + help_topic_valid_triplet(paths); + exit(EXIT_FAILURE); + } + } + + if (auto command_function = find_command(args.command, get_available_commands_type_a())) + { + return command_function(args, paths, default_target_triplet); + } + + return invalid_command(args.command); +} + +static void loadConfig() +{ + fs::path localappdata; + { + // Config path in AppDataLocal + wchar_t* localappdatapath = nullptr; + if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localappdatapath)) + __fastfail(1); + localappdata = localappdatapath; + CoTaskMemFree(localappdatapath); + } + + try + { + std::string config_contents = Files::get_contents(localappdata / "vcpkg" / "config").get_or_throw(); + + std::unordered_map<std::string, std::string> keys; + auto pghs = parse_paragraphs(config_contents); + if (pghs.size() > 0) + keys = pghs[0]; + + for (size_t x = 1; x < pghs.size(); ++x) + { + for (auto&& p : pghs[x]) + keys.insert(p); + } + + auto user_id = keys["User-Id"]; + auto user_time = keys["User-Since"]; + Checks::check_throw(!user_id.empty() && !user_time.empty(), ""); // Use as goto to the catch statement + + SetUserInformation(user_id, user_time); + return; + } + catch (...) + { + } + + // config file not found, could not be read, or invalid + std::string user_id, user_time; + InitUserInformation(user_id, user_time); + SetUserInformation(user_id, user_time); + try + { + std::error_code ec; + fs::create_directory(localappdata / "vcpkg", ec); + std::ofstream(localappdata / "vcpkg" / "config", std::ios_base::out | std::ios_base::trunc) + << "User-Id: " << user_id << "\n" + << "User-Since: " << user_time << "\n"; + } + catch (...) + { + } +} + +static System::Stopwatch g_timer; + +static std::string trim_path_from_command_line(const std::string& full_command_line) +{ + Checks::check_exit(full_command_line.size() > 0, "Internal failure - cannot have empty command line"); + + if (full_command_line[0] == '"') + { + auto it = std::find(full_command_line.cbegin() + 1, full_command_line.cend(), '"'); + if (it != full_command_line.cend()) // Skip over the quote + ++it; + while (it != full_command_line.cend() && *it == ' ') // Skip over a space + ++it; + return std::string(it, full_command_line.cend()); + } + + auto it = std::find(full_command_line.cbegin(), full_command_line.cend(), ' '); + while (it != full_command_line.cend() && *it == ' ') + ++it; + return std::string(it, full_command_line.cend()); +} + +int wmain(const int argc, const wchar_t* const* const argv) +{ + if (argc == 0) + std::abort(); + + std::cout.sync_with_stdio(false); + std::cout.imbue(std::locale::classic()); + + g_timer.start(); + atexit([]() + { + g_timer.stop(); + TrackMetric("elapsed_us", g_timer.microseconds()); + Flush(); + }); + + TrackProperty("version", version()); + + const std::string trimmed_command_line = trim_path_from_command_line(Strings::utf16_to_utf8(GetCommandLineW())); + TrackProperty("cmdline", trimmed_command_line); + loadConfig(); + + const vcpkg_cmd_arguments args = vcpkg_cmd_arguments::create_from_command_line(argc, argv); + + if (args.printmetrics != opt_bool::unspecified) + SetPrintMetrics(args.printmetrics == opt_bool::enabled); + if (args.sendmetrics != opt_bool::unspecified) + SetSendMetrics(args.sendmetrics == opt_bool::enabled); + + if (args.debug != opt_bool::unspecified) + { + g_debugging = (args.debug == opt_bool::enabled); + } + + if (g_debugging) + { + inner(args); + exit(EXIT_FAILURE); + } + + std::string exc_msg; + try + { + inner(args); + exit(EXIT_FAILURE); + } + catch (std::exception& e) + { + exc_msg = e.what(); + } + catch (...) + { + exc_msg = "unknown error(...)"; + } + TrackProperty("error", exc_msg); + std::cerr + << "vcpkg.exe has crashed.\n" + << "Please send an email to:\n" + << " vcpkg@microsoft.com\n" + << "containing a brief summary of what you were trying to do and the following data blob:\n" + << "\n" + << "Version=" << version() << "\n" + << "EXCEPTION='" << exc_msg << "'\n" + << "CMD=\n"; + for (int x = 0; x < argc; ++x) + std::cerr << argv[x] << "|\n"; + std::cerr + << "\n"; +} diff --git a/toolsrc/src/metrics.cpp b/toolsrc/src/metrics.cpp new file mode 100644 index 000000000..610c71ed1 --- /dev/null +++ b/toolsrc/src/metrics.cpp @@ -0,0 +1,425 @@ +#include "metrics.h" +#include <utility> +#include <array> +#include <string> +#include <iostream> +#include <vector> +#include <sys/timeb.h> +#include <time.h> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include <winhttp.h> +#include <fstream> +#include <filesystem> +#include "vcpkg_Strings.h" +#include "vcpkg_System.h" + +namespace fs = std::tr2::sys; + +namespace vcpkg +{ + static std::string GetCurrentDateTime() + { + struct tm newtime; + time_t now; + int milli; + std::array<char, 80> date; + date.fill(0); + + struct _timeb timebuffer; + + _ftime_s(&timebuffer); + now = timebuffer.time; + milli = timebuffer.millitm; + + errno_t err = gmtime_s(&newtime, &now); + if (err) + { + return ""; + } + + strftime(&date[0], date.size(), "%Y-%m-%dT%H:%M:%S", &newtime); + return std::string(&date[0]) + "." + std::to_string(milli) + "Z"; + } + + static std::string GenerateRandomUUID() + { + int partSizes[] = {8, 4, 4, 4, 12}; + char uuid[37]; + memset(uuid, 0, sizeof(uuid)); + int num; + srand(static_cast<int>(time(nullptr))); + int index = 0; + for (int part = 0; part < 5; part++) + { + if (part > 0) + { + uuid[index] = '-'; + index++; + } + + // Generating UUID format version 4 + // http://en.wikipedia.org/wiki/Universally_unique_identifier + for (int i = 0; i < partSizes[part]; i++ , index++) + { + if (part == 2 && i == 0) + { + num = 4; + } + else if (part == 4 && i == 0) + { + num = (rand() % 4) + 8; + } + else + { + num = rand() % 16; + } + + if (num < 10) + { + uuid[index] = static_cast<char>('0' + num); + } + else + { + uuid[index] = static_cast<char>('a' + (num - 10)); + } + } + } + + return uuid; + } + + static const std::string& get_session_id() + { + static const std::string id = GenerateRandomUUID(); + return id; + } + + static std::string to_json_string(const std::string& str) + { + std::string encoded = "\""; + for (auto&& ch : str) + { + if (ch == '\\') + { + encoded.append("\\\\"); + } + else if (ch == '"') + { + encoded.append("\\\""); + } + else if (ch < 0x20 || ch >= 0x80) + { + // Note: this treats incoming Strings as Latin-1 + static constexpr const char hex[16] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + encoded.append("\\u00"); + encoded.push_back(hex[ch / 16]); + encoded.push_back(hex[ch % 16]); + } + else + { + encoded.push_back(ch); + } + } + encoded.push_back('"'); + return encoded; + } + + static std::string get_os_version_string() + { + std::wstring path; + path.resize(MAX_PATH); + auto n = GetSystemDirectoryW(&path[0], static_cast<UINT>(path.size())); + path.resize(n); + path += L"\\kernel32.dll"; + + auto versz = GetFileVersionInfoSizeW(path.c_str(), nullptr); + if (versz == 0) + return ""; + + std::vector<char> verbuf; + verbuf.resize(versz); + + if (!GetFileVersionInfoW(path.c_str(), 0, static_cast<DWORD>(verbuf.size()), &verbuf[0])) + return ""; + + void* rootblock; + UINT rootblocksize; + if (!VerQueryValueW(&verbuf[0], L"\\", &rootblock, &rootblocksize)) + return ""; + + auto rootblock_ffi = static_cast<VS_FIXEDFILEINFO *>(rootblock); + + return Strings::format("%d.%d.%d", + static_cast<int>(HIWORD(rootblock_ffi->dwProductVersionMS)), + static_cast<int>(LOWORD(rootblock_ffi->dwProductVersionMS)), + static_cast<int>(HIWORD(rootblock_ffi->dwProductVersionLS))); + } + + struct MetricMessage + { + std::string user_id = GenerateRandomUUID(); + std::string user_timestamp; + std::string timestamp = GetCurrentDateTime(); + std::string properties; + std::string measurements; + + void TrackProperty(const std::string& name, const std::string& value) + { + if (properties.size() != 0) + properties.push_back(','); + properties.append(to_json_string(name)); + properties.push_back(':'); + properties.append(to_json_string(value)); + } + + void TrackMetric(const std::string& name, double value) + { + if (measurements.size() != 0) + measurements.push_back(','); + measurements.append(to_json_string(name)); + measurements.push_back(':'); + measurements.append(std::to_string(value)); + } + + std::string format_event_data_template() const + { + const std::string& session_id = get_session_id(); + return Strings::format(R"([{ + "ver": 1, + "name": "Microsoft.ApplicationInsights.Event", + "time": "%s", + "sampleRate": 100.000000, + "seq": "0:0", + "iKey": "b4e88960-4393-4dd9-ab8e-97e8fe6d7603", + "flags": 0.000000, + "tags": { + "ai.device.os": "Windows", + "ai.device.osVersion": "%s", + "ai.session.id": "%s", + "ai.user.id": "%s", + "ai.user.accountAcquisitionDate": "%s" + }, + "data": { + "baseType": "EventData", + "baseData": { + "ver": 2, + "name": "commandline_test7", + "properties": { %s }, + "measurements": { %s } + } + } +}])", + timestamp, + get_os_version_string(), + session_id, + user_id, + user_timestamp, + properties, + measurements); + } + }; + + static MetricMessage g_metricmessage; + static bool g_should_send_metrics = +#if defined(NDEBUG) && (DISABLE_METRICS == 0) +true +#else + false +#endif + ; + static bool g_should_print_metrics = false; + + bool GetCompiledMetricsEnabled() + { + return DISABLE_METRICS == 0; + } + + void SetUserInformation(const std::string& user_id, const std::string& first_use_time) + { + g_metricmessage.user_id = user_id; + g_metricmessage.user_timestamp = first_use_time; + } + + void InitUserInformation(std::string& user_id, std::string& first_use_time) + { + user_id = GenerateRandomUUID(); + first_use_time = GetCurrentDateTime(); + } + + void SetSendMetrics(bool should_send_metrics) + { + g_should_send_metrics = should_send_metrics; + } + + void SetPrintMetrics(bool should_print_metrics) + { + g_should_print_metrics = should_print_metrics; + } + + void TrackMetric(const std::string& name, double value) + { + g_metricmessage.TrackMetric(name, value); + } + + void TrackProperty(const std::string& name, const std::wstring& value) + { + // Note: this is not valid UTF-16 -> UTF-8, it just yields a close enough approximation for our purposes. + std::string converted_value; + converted_value.resize(value.size()); + std::transform( + value.begin(), value.end(), + converted_value.begin(), + [](wchar_t ch) + { + return static_cast<char>(ch); + }); + + g_metricmessage.TrackProperty(name, converted_value); + } + + void TrackProperty(const std::string& name, const std::string& value) + { + g_metricmessage.TrackProperty(name, value); + } + + void Upload(const std::string& payload) + { + HINTERNET hSession = nullptr, hConnect = nullptr, hRequest = nullptr; + BOOL bResults = FALSE; + + hSession = WinHttpOpen(L"vcpkg/1.0", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + if (hSession) + hConnect = WinHttpConnect(hSession, L"dc.services.visualstudio.com", INTERNET_DEFAULT_HTTPS_PORT, 0); + + if (hConnect) + hRequest = WinHttpOpenRequest(hConnect, + L"POST", + L"/v2/track", + nullptr, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_SECURE); + + if (hRequest) + { + if (MAXDWORD <= payload.size()) + abort(); + std::wstring hdrs = L"Content-Type: application/json\r\n"; + bResults = WinHttpSendRequest(hRequest, + hdrs.c_str(), static_cast<DWORD>(hdrs.size()), + (void*)&payload[0], static_cast<DWORD>(payload.size()), static_cast<DWORD>(payload.size()), + 0); + } + + if (bResults) + { + bResults = WinHttpReceiveResponse(hRequest, nullptr); + } + + DWORD http_code = 0, junk = sizeof(DWORD); + + if (bResults) + { + bResults = WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, nullptr, &http_code, &junk, WINHTTP_NO_HEADER_INDEX); + } + + std::vector<char> responseBuffer; + if (bResults) + { + DWORD availableData = 0, readData = 0, totalData = 0; + + while ((bResults = WinHttpQueryDataAvailable(hRequest, &availableData)) && availableData > 0) + { + responseBuffer.resize(responseBuffer.size() + availableData); + + bResults = WinHttpReadData(hRequest, &responseBuffer.data()[totalData], availableData, &readData); + + if (!bResults) + { + break; + } + + totalData += readData; + + responseBuffer.resize(totalData); + } + } + + if (!bResults) + { +#ifndef NDEBUG + __debugbreak(); + auto err = GetLastError(); + std::cerr << "[DEBUG] failed to connect to server: " << err << "\n"; +#endif + } + + if (hRequest) + WinHttpCloseHandle(hRequest); + if (hConnect) + WinHttpCloseHandle(hConnect); + if (hSession) + WinHttpCloseHandle(hSession); + } + + static fs::path get_bindir() + { + wchar_t buf[_MAX_PATH ]; + int bytes = GetModuleFileNameW(nullptr, buf, _MAX_PATH); + if (bytes == 0) + std::abort(); + return fs::path(buf, buf + bytes); + } + + void Flush() + { + std::string payload = g_metricmessage.format_event_data_template(); + if (g_should_print_metrics) + std::cerr << payload << "\n"; + if (!g_should_send_metrics) + return; + + // Upload(payload); + + wchar_t temp_folder[MAX_PATH]; + GetTempPathW(MAX_PATH, temp_folder); + + const fs::path temp_folder_path = temp_folder; + const fs::path temp_folder_path_exe = temp_folder_path / "vcpkgmetricsuploader.exe"; + + if (true) + { + const fs::path exe_path = []() -> fs::path + { + auto vcpkgdir = get_bindir().parent_path(); + auto path = vcpkgdir / "vcpkgmetricsuploader.exe"; + if (fs::exists(path)) + return path; + + path = vcpkgdir / "scripts" / "vcpkgmetricsuploader.exe"; + if (fs::exists(path)) + return path; + + return L""; + }(); + + std::error_code ec; + fs::copy_file(exe_path, temp_folder_path_exe, fs::copy_options::skip_existing, ec); + if (ec) + return; + } + + const fs::path vcpkg_metrics_txt_path = temp_folder_path / ("vcpkg" + GenerateRandomUUID() + ".txt"); + std::ofstream(vcpkg_metrics_txt_path) << payload; + + const std::wstring cmdLine = Strings::format(L"start %s %s", temp_folder_path_exe.native(), vcpkg_metrics_txt_path.native()); + System::cmd_execute(cmdLine); + } +} diff --git a/toolsrc/src/package_spec.cpp b/toolsrc/src/package_spec.cpp new file mode 100644 index 000000000..ece5f91e9 --- /dev/null +++ b/toolsrc/src/package_spec.cpp @@ -0,0 +1,46 @@ +#include "package_spec.h" + +namespace vcpkg +{ + expected<package_spec> parse(const std::string& spec, const triplet& default_target_triplet) + { + auto pos = spec.find(':'); + if (pos == std::string::npos) + { + return package_spec{spec, default_target_triplet}; + } + + auto pos2 = spec.find(':', pos + 1); + if (pos2 != std::string::npos) + { + return std::error_code(package_spec_parse_result::too_many_colons); + } + + return package_spec{spec.substr(0, pos), spec.substr(pos + 1)}; + } + + std::string package_spec::dir() const + { + return Strings::format("%s_%s", this->name, this->target_triplet); + } + + std::string to_string(const package_spec& spec) + { + return Strings::format("%s:%s", spec.name, spec.target_triplet); + } + + std::string to_printf_arg(const package_spec& spec) + { + return to_string(spec); + } + + bool operator==(const package_spec& left, const package_spec& right) + { + return left.name == right.name && left.target_triplet == right.target_triplet; + } + + std::ostream& operator<<(std::ostream& os, const package_spec& spec) + { + return os << to_string(spec); + } +} diff --git a/toolsrc/src/package_spec_parse_result.cpp b/toolsrc/src/package_spec_parse_result.cpp new file mode 100644 index 000000000..757b6df53 --- /dev/null +++ b/toolsrc/src/package_spec_parse_result.cpp @@ -0,0 +1,45 @@ +#include <package_spec.h> +#include <system_error> +#include "package_spec_parse_result.h" + +namespace vcpkg +{ + const char* package_spec_parse_result_category_impl::name() const noexcept + { + return "package_spec_parse_result"; + } + + std::string package_spec_parse_result_category_impl::message(int ev) const noexcept + { + switch (static_cast<package_spec_parse_result>(ev)) + { + case package_spec_parse_result::success: + return "OK"; + case package_spec_parse_result::too_many_colons: + return "Too many colons"; + default: + Checks::unreachable(); + } + } + + const std::error_category& package_spec_parse_result_category() + { + static package_spec_parse_result_category_impl instance; + return instance; + } + + std::error_code make_error_code(package_spec_parse_result e) + { + return std::error_code(static_cast<int>(e), package_spec_parse_result_category()); + } + + package_spec_parse_result to_package_spec_parse_result(int i) + { + return static_cast<package_spec_parse_result>(i); + } + + package_spec_parse_result to_package_spec_parse_result(std::error_code ec) + { + return to_package_spec_parse_result(ec.value()); + } +} diff --git a/toolsrc/src/post_build_lint.cpp b/toolsrc/src/post_build_lint.cpp new file mode 100644 index 000000000..3c4c5938d --- /dev/null +++ b/toolsrc/src/post_build_lint.cpp @@ -0,0 +1,356 @@ +#include <filesystem> +#include "vcpkg_paths.h" +#include "package_spec.h" +#include <iterator> +#include <functional> +#include "vcpkg_System.h" + +namespace fs = std::tr2::sys; + +namespace vcpkg +{ + enum class lint_status + { + SUCCESS = 0, + ERROR = 1 + }; + + static const fs::path DUMPBIN_EXE = R"(C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin\dumpbin.exe)"; + + namespace + { + void print_vector_of_files(const std::vector<fs::path>& paths) + { + System::println(""); + for (const fs::path& p : paths) + { + System::println(" %s", p.generic_string()); + } + System::println(""); + } + + template <class Pred> + void recursive_find_matching_paths_in_dir(const fs::path& dir, const Pred predicate, std::vector<fs::path>& output) + { + std::copy_if(fs::recursive_directory_iterator(dir), fs::recursive_directory_iterator(), std::back_inserter(output), predicate); + } + + void recursive_find_files_with_extension_in_dir(const fs::path& dir, const std::string& extension, std::vector<fs::path>& output) + { + recursive_find_matching_paths_in_dir(dir, [&extension](const fs::path& current) + { + return !fs::is_directory(current) && current.extension() == extension; + }, output); + } + } + + static lint_status check_for_files_in_include_directory(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path include_dir = paths.packages / spec.dir() / "include"; + if (!fs::exists(include_dir) || fs::is_empty(include_dir)) + { + System::println(System::color::warning, "The folder /include is empty. This indicates the library was not correctly installed."); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_files_in_debug_include_directory(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path debug_include_dir = paths.packages / spec.dir() / "debug" / "include"; + std::vector<fs::path> files_found; + + recursive_find_matching_paths_in_dir(debug_include_dir, [&](const fs::path& current) + { + return !fs::is_directory(current) && current.extension() != ".ifc"; + }, files_found); + + if (!files_found.empty()) + { + System::println(System::color::warning, "Include files should not be duplicated into the /debug/include directory. If this cannot be disabled in the project cmake, use\n" + " file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)" + ); + return + lint_status::ERROR; + } + + return + lint_status::SUCCESS; + } + + static lint_status check_for_files_in_debug_share_directory(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path debug_share = paths.packages / spec.dir() / "debug" / "share"; + + if (fs::exists(debug_share) && !fs::is_empty(debug_share)) + { + System::println(System::color::warning, "No files should be present in /debug/share"); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_folder_lib_cmake(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path lib_cmake = paths.packages / spec.dir() / "lib" / "cmake"; + if (fs::exists(lib_cmake)) + { + System::println(System::color::warning, "The /lib/cmake folder should be moved to just /cmake"); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_misplaced_cmake_files(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path current_packages_dir = paths.packages / spec.dir(); + std::vector<fs::path> misplaced_cmake_files; + recursive_find_files_with_extension_in_dir(current_packages_dir / "cmake", ".cmake", misplaced_cmake_files); + recursive_find_files_with_extension_in_dir(current_packages_dir / "debug" / "cmake", ".cmake", misplaced_cmake_files); + recursive_find_files_with_extension_in_dir(current_packages_dir / "lib" / "cmake", ".cmake", misplaced_cmake_files); + recursive_find_files_with_extension_in_dir(current_packages_dir / "debug" / "lib" / "cmake", ".cmake", misplaced_cmake_files); + + if (!misplaced_cmake_files.empty()) + { + System::println(System::color::warning, "The following cmake files were found outside /share/%s. Please place cmake files in /share/%s.", spec.name, spec.name); + print_vector_of_files(misplaced_cmake_files); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_folder_debug_lib_cmake(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path lib_cmake_debug = paths.packages / spec.dir() / "debug" / "lib" / "cmake"; + if (fs::exists(lib_cmake_debug)) + { + System::println(System::color::warning, "The /debug/lib/cmake folder should be moved to just /debug/cmake"); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_dlls_in_lib_dirs(const package_spec& spec, const vcpkg_paths& paths) + { + std::vector<fs::path> dlls; + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "lib", ".dll", dlls); + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "lib", ".dll", dlls); + + if (!dlls.empty()) + { + System::println(System::color::warning, "\nThe following dlls were found in /lib and /debug/lib. Please move them to /bin or /debug/bin, respectively."); + print_vector_of_files(dlls); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_copyright_file(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path copyright_file = paths.packages / spec.dir() / "share" / spec.name / "copyright"; + if (fs::exists(copyright_file)) + { + return lint_status::SUCCESS; + } + const fs::path current_buildtrees_dir = paths.buildtrees / spec.name; + const fs::path current_buildtrees_dir_src = current_buildtrees_dir / "src"; + + std::vector<fs::path> potential_copyright_files; + // Only searching one level deep + for (auto it = fs::recursive_directory_iterator(current_buildtrees_dir_src); it != fs::recursive_directory_iterator(); ++it) + { + if (it.depth() > 1) + { + continue; + } + + const std::string filename = it->path().filename().string(); + if (filename == "LICENSE" || filename == "LICENSE.txt" || filename == "COPYING") + { + potential_copyright_files.push_back(it->path()); + } + } + + System::println(System::color::warning, "The software license must be available at ${CURRENT_PACKAGES_DIR}/share/%s/copyright .", spec.name); + if (potential_copyright_files.size() == 1) // if there is only one candidate, provide the cmake lines needed to place it in the proper location + { + const fs::path found_file = potential_copyright_files[0]; + const fs::path relative_path = found_file.string().erase(0, current_buildtrees_dir.string().size() + 1); // The +1 is needed to remove the "/" + System::println("\n file(COPY ${CURRENT_BUILDTREES_DIR}/%s DESTINATION ${CURRENT_PACKAGES_DIR}/share/%s)\n" + " file(RENAME ${CURRENT_PACKAGES_DIR}/share/%s/%s ${CURRENT_PACKAGES_DIR}/share/%s/copyright)", + relative_path.generic_string(), spec.name, spec.name, found_file.filename().generic_string(), spec.name); + return lint_status::ERROR; + } + + if (potential_copyright_files.size() > 1) + { + System::println(System::color::warning, "The following files are potential copyright files:"); + print_vector_of_files(potential_copyright_files); + } + + const fs::path current_packages_dir = paths.packages / spec.dir(); + System::println(" %s/share/%s/copyright", current_packages_dir.generic_string(), spec.name); + + return lint_status::ERROR; + } + + static lint_status check_for_exes(const package_spec& spec, const vcpkg_paths& paths) + { + std::vector<fs::path> exes; + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "bin", ".exe", exes); + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "bin", ".exe", exes); + + if (!exes.empty()) + { + System::println(System::color::warning, "The following EXEs were found in /bin and /debug/bin. EXEs are not valid distribution targets."); + print_vector_of_files(exes); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_exports_of_dlls(const std::vector<fs::path>& dlls) + { + std::vector<fs::path> dlls_with_no_exports; + for (const fs::path& dll : dlls) + { + const std::wstring cmd_line = Strings::format(LR"("%s" /exports "%s")", DUMPBIN_EXE.native(), dll.native()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + if (ec_data.output.find("ordinal hint RVA name") == std::string::npos) + { + dlls_with_no_exports.push_back(dll); + } + } + + if (!dlls_with_no_exports.empty()) + { + System::println(System::color::warning, "The following DLLs have no exports:"); + print_vector_of_files(dlls_with_no_exports); + System::println(System::color::warning, "DLLs without any exports are likely a bug in the build script."); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static lint_status check_uwp_bit_of_dlls(const std::string& expected_system_name, const std::vector<fs::path>& dlls) + { + if (expected_system_name != "uwp") + { + return lint_status::SUCCESS; + } + + std::vector<fs::path> dlls_with_improper_uwp_bit; + for (const fs::path& dll : dlls) + { + const std::wstring cmd_line = Strings::format(LR"("%s" /headers "%s")", DUMPBIN_EXE.native(), dll.native()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + if (ec_data.output.find("App Container") == std::string::npos) + { + dlls_with_improper_uwp_bit.push_back(dll); + } + } + + if (!dlls_with_improper_uwp_bit.empty()) + { + System::println(System::color::warning, "The following DLLs do not have the App Container bit set:"); + print_vector_of_files(dlls_with_improper_uwp_bit); + System::println(System::color::warning, "This bit is required for Windows Store apps."); + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + struct file_and_arch + { + fs::path file; + std::string actual_arch; + }; + + static lint_status check_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) + { + std::vector<file_and_arch> binaries_with_invalid_architecture; + for (const fs::path& f : files) + { + const std::wstring cmd_line = Strings::format(LR"("%s" /headers "%s" | findstr machine)", DUMPBIN_EXE.native(), f.native()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + if (Strings::case_insensitive_find(ec_data.output, expected_architecture) == ec_data.output.end()) + { + binaries_with_invalid_architecture.push_back({f, ec_data.output}); + } + } + + if (!binaries_with_invalid_architecture.empty()) + { + System::println(System::color::warning, "The following files were built for an incorrect architecture:"); + System::println(""); + for (const file_and_arch& b : binaries_with_invalid_architecture) + { + System::println(" %s", b.file.generic_string()); + System::println("Expected %s, but was:\n %s", expected_architecture, b.actual_arch); + } + System::println(""); + + return lint_status::ERROR; + } + + return lint_status::SUCCESS; + } + + static void operator +=(unsigned int& left, const lint_status& right) + { + left += static_cast<unsigned int>(right); + } + + void perform_all_checks(const package_spec& spec, const vcpkg_paths& paths) + { + System::println("-- Performing post-build validation"); + unsigned int error_count = 0; + error_count += check_for_files_in_include_directory(spec, paths); + error_count += check_for_files_in_debug_include_directory(spec, paths); + error_count += check_for_files_in_debug_share_directory(spec, paths); + error_count += check_folder_lib_cmake(spec, paths); + error_count += check_for_misplaced_cmake_files(spec, paths); + error_count += check_folder_debug_lib_cmake(spec, paths); + error_count += check_for_dlls_in_lib_dirs(spec, paths); + error_count += check_for_copyright_file(spec, paths); + error_count += check_for_exes(spec, paths); + + std::vector<fs::path> dlls; + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "bin", ".dll", dlls); + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "bin", ".dll", dlls); + + error_count += check_exports_of_dlls(dlls); + error_count += check_uwp_bit_of_dlls(spec.target_triplet.system(), dlls); + error_count += check_architecture(spec.target_triplet.architecture(), dlls); + + std::vector<fs::path> libs; + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "lib", ".lib", libs); + recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "lib", ".lib", libs); + + error_count += check_architecture(spec.target_triplet.architecture(), libs); + + if (error_count != 0) + { + const fs::path portfile = paths.ports / spec.name / "portfile.cmake"; + System::println(System::color::error, "Found %d error(s). Please correct the portfile:\n %s", error_count, portfile.string()); + exit(EXIT_FAILURE); + } + + System::println("-- Performing post-build validation done"); + } +} diff --git a/toolsrc/src/test.cpp b/toolsrc/src/test.cpp new file mode 100644 index 000000000..82113abaa --- /dev/null +++ b/toolsrc/src/test.cpp @@ -0,0 +1,347 @@ +#include "CppUnitTest.h" +#include "vcpkg.h" + +#pragma comment(lib,"version") +#pragma comment(lib,"winhttp") + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace Microsoft { namespace VisualStudio { namespace CppUnitTestFramework +{ + template <> + inline std::wstring ToString<vcpkg::package_spec_parse_result>(const vcpkg::package_spec_parse_result& t) + { + return ToString(static_cast<uint32_t>(t)); + } +}}} + +namespace UnitTest1 +{ + TEST_CLASS(ControlParsing) + { + public: + TEST_METHOD(SourceParagraph_Construct_Minimum) + { + vcpkg::SourceParagraph pgh({ + {"Source", "zlib"}, + {"Version", "1.2.8"} + }); + + Assert::AreEqual("zlib", pgh.name.c_str()); + Assert::AreEqual("1.2.8", pgh.version.c_str()); + Assert::AreEqual("", pgh.maintainer.c_str()); + Assert::AreEqual("", pgh.description.c_str()); + Assert::AreEqual(size_t(0), pgh.depends.size()); + } + + TEST_METHOD(SourceParagraph_Construct_Maximum) + { + vcpkg::SourceParagraph pgh({ + {"Source", "s"}, + {"Version", "v"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Build-Depends", "bd"} + }); + Assert::AreEqual("s", pgh.name.c_str()); + Assert::AreEqual("v", pgh.version.c_str()); + Assert::AreEqual("m", pgh.maintainer.c_str()); + Assert::AreEqual("d", pgh.description.c_str()); + Assert::AreEqual(size_t(1), pgh.depends.size()); + Assert::AreEqual("bd", pgh.depends[0].c_str()); + } + + TEST_METHOD(SourceParagraph_Two_Depends) + { + vcpkg::SourceParagraph pgh({ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl"} + }); + + Assert::AreEqual(size_t(2), pgh.depends.size()); + Assert::AreEqual("z", pgh.depends[0].c_str()); + Assert::AreEqual("openssl", pgh.depends[1].c_str()); + } + + TEST_METHOD(SourceParagraph_Three_Depends) + { + vcpkg::SourceParagraph pgh({ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl, xyz"} + }); + + Assert::AreEqual(size_t(3), pgh.depends.size()); + Assert::AreEqual("z", pgh.depends[0].c_str()); + Assert::AreEqual("openssl", pgh.depends[1].c_str()); + Assert::AreEqual("xyz", pgh.depends[2].c_str()); + } + + TEST_METHOD(BinaryParagraph_Construct_Minimum) + { + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "a"}, + {"Multi-Arch", "same"}, + }); + + Assert::AreEqual("zlib", pgh.name.c_str()); + Assert::AreEqual("1.2.8", pgh.version.c_str()); + Assert::AreEqual("", pgh.maintainer.c_str()); + Assert::AreEqual("", pgh.description.c_str()); + Assert::AreEqual("a", pgh.target_triplet.value.c_str()); + Assert::AreEqual(size_t(0), pgh.depends.size()); + } + + TEST_METHOD(BinaryParagraph_Construct_Maximum) + { + vcpkg::BinaryParagraph pgh({ + {"Package", "s"}, + {"Version", "v"}, + {"Architecture", "a"}, + {"Multi-Arch", "same"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Depends", "bd"} + }); + Assert::AreEqual("s", pgh.name.c_str()); + Assert::AreEqual("v", pgh.version.c_str()); + Assert::AreEqual("m", pgh.maintainer.c_str()); + Assert::AreEqual("d", pgh.description.c_str()); + Assert::AreEqual(size_t(1), pgh.depends.size()); + Assert::AreEqual("bd", pgh.depends[0].c_str()); + } + + TEST_METHOD(BinaryParagraph_Three_Depends) + { + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "a"}, + {"Multi-Arch", "same"}, + {"Depends", "a, b, c"}, + }); + + Assert::AreEqual(size_t(3), pgh.depends.size()); + Assert::AreEqual("a", pgh.depends[0].c_str()); + Assert::AreEqual("b", pgh.depends[1].c_str()); + Assert::AreEqual("c", pgh.depends[2].c_str()); + } + + TEST_METHOD(parse_paragraphs_empty) + { + const char* str = ""; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::IsTrue(pghs.empty()); + } + + TEST_METHOD(parse_paragraphs_one_field) + { + const char* str = "f1: v1"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual(size_t(1), pghs[0].size()); + Assert::AreEqual("v1", pghs[0]["f1"].c_str()); + } + + TEST_METHOD(parse_paragraphs_one_pgh) + { + const char* str = + "f1: v1\n" + "f2: v2"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual(size_t(2), pghs[0].size()); + Assert::AreEqual("v1", pghs[0]["f1"].c_str()); + Assert::AreEqual("v2", pghs[0]["f2"].c_str()); + } + + TEST_METHOD(parse_paragraphs_two_pgh) + { + const char* str = + "f1: v1\n" + "f2: v2\n" + "\n" + "f3: v3\n" + "f4: v4"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(2), pghs.size()); + Assert::AreEqual(size_t(2), pghs[0].size()); + Assert::AreEqual("v1", pghs[0]["f1"].c_str()); + Assert::AreEqual("v2", pghs[0]["f2"].c_str()); + Assert::AreEqual(size_t(2), pghs[1].size()); + Assert::AreEqual("v3", pghs[1]["f3"].c_str()); + Assert::AreEqual("v4", pghs[1]["f4"].c_str()); + } + + TEST_METHOD(parse_paragraphs_field_names) + { + const char* str = + "1:\n" + "f:\n" + "F:\n" + "0:\n" + "F-2:\n"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual(size_t(5), pghs[0].size()); + } + + TEST_METHOD(parse_paragraphs_multiple_blank_lines) + { + const char* str = + "f1: v1\n" + "f2: v2\n" + "\n" + "\n" + "f3: v3\n" + "f4: v4"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(2), pghs.size()); + } + + TEST_METHOD(parse_paragraphs_empty_fields) + { + const char* str = + "f1:\n" + "f2: "; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual(size_t(2), pghs[0].size()); + Assert::AreEqual("", pghs[0]["f1"].c_str()); + Assert::AreEqual("", pghs[0]["f2"].c_str()); + Assert::AreEqual(size_t(2), pghs[0].size()); + } + + TEST_METHOD(parse_paragraphs_multiline_fields) + { + const char* str = + "f1: simple\n" + " f1\r\n" + "f2:\r\n" + " f2\r\n" + " continue\r\n"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual("simple\n f1", pghs[0]["f1"].c_str()); + Assert::AreEqual("\n f2\n continue", pghs[0]["f2"].c_str()); + } + + TEST_METHOD(parse_paragraphs_crlfs) + { + const char* str = + "f1: v1\r\n" + "f2: v2\r\n" + "\r\n" + "f3: v3\r\n" + "f4: v4"; + auto pghs = vcpkg::parse_paragraphs(str); + Assert::AreEqual(size_t(2), pghs.size()); + Assert::AreEqual(size_t(2), pghs[0].size()); + Assert::AreEqual("v1", pghs[0]["f1"].c_str()); + Assert::AreEqual("v2", pghs[0]["f2"].c_str()); + Assert::AreEqual(size_t(2), pghs[1].size()); + Assert::AreEqual("v3", pghs[1]["f3"].c_str()); + Assert::AreEqual("v4", pghs[1]["f4"].c_str()); + } + + TEST_METHOD(BinaryParagraph_serialize_min) + { + std::stringstream ss; + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "a"}, + {"Multi-Arch", "same"}, + }); + ss << pgh; + auto pghs = vcpkg::parse_paragraphs(ss.str()); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual(size_t(4), pghs[0].size()); + Assert::AreEqual("zlib", pghs[0]["Package"].c_str()); + Assert::AreEqual("1.2.8", pghs[0]["Version"].c_str()); + Assert::AreEqual("a", pghs[0]["Architecture"].c_str()); + Assert::AreEqual("same", pghs[0]["Multi-Arch"].c_str()); + } + + TEST_METHOD(BinaryParagraph_serialize_max) + { + std::stringstream ss; + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "a"}, + {"Description", "first line\n second line"}, + {"Maintainer", "abc <abc@abc.abc>"}, + {"Depends", "dep"}, + {"Multi-Arch", "same"}, + }); + ss << pgh; + auto pghs = vcpkg::parse_paragraphs(ss.str()); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual(size_t(7), pghs[0].size()); + Assert::AreEqual("zlib", pghs[0]["Package"].c_str()); + Assert::AreEqual("1.2.8", pghs[0]["Version"].c_str()); + Assert::AreEqual("a", pghs[0]["Architecture"].c_str()); + Assert::AreEqual("same", pghs[0]["Multi-Arch"].c_str()); + Assert::AreEqual("first line\n second line", pghs[0]["Description"].c_str()); + Assert::AreEqual("dep", pghs[0]["Depends"].c_str()); + } + + TEST_METHOD(BinaryParagraph_serialize_multiple_deps) + { + std::stringstream ss; + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "a"}, + {"Multi-Arch", "same"}, + {"Depends", "a, b, c"}, + }); + ss << pgh; + auto pghs = vcpkg::parse_paragraphs(ss.str()); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual("a, b, c", pghs[0]["Depends"].c_str()); + } + + TEST_METHOD(package_spec_parse) + { + vcpkg::expected<vcpkg::package_spec> spec = vcpkg::parse("zlib", vcpkg::triplet::X86_WINDOWS); + Assert::AreEqual(vcpkg::package_spec_parse_result::success, vcpkg::to_package_spec_parse_result(spec.error_code())); + Assert::AreEqual("zlib", spec.get()->name.c_str()); + Assert::AreEqual(vcpkg::triplet::X86_WINDOWS.value, spec.get()->target_triplet.value); + } + + TEST_METHOD(package_spec_parse_with_arch) + { + vcpkg::expected<vcpkg::package_spec> spec = vcpkg::parse("zlib:x64-uwp", vcpkg::triplet::X86_WINDOWS); + Assert::AreEqual(vcpkg::package_spec_parse_result::success, vcpkg::to_package_spec_parse_result(spec.error_code())); + Assert::AreEqual("zlib", spec.get()->name.c_str()); + Assert::AreEqual(vcpkg::triplet::X64_UWP.value, spec.get()->target_triplet.value); + } + + TEST_METHOD(package_spec_parse_with_multiple_colon) + { + auto ec = vcpkg::parse("zlib:x86-uwp:", vcpkg::triplet::X86_WINDOWS).error_code(); + Assert::AreEqual(vcpkg::package_spec_parse_result::too_many_colons, vcpkg::to_package_spec_parse_result(ec)); + } + + TEST_METHOD(utf8_to_utf16) + { + auto str = vcpkg::Strings::utf8_to_utf16("abc"); + Assert::AreEqual(L"abc", str.c_str()); + } + + TEST_METHOD(utf8_to_utf16_with_whitespace) + { + auto str = vcpkg::Strings::utf8_to_utf16("abc -x86-windows"); + Assert::AreEqual(L"abc -x86-windows", str.c_str()); + } + }; + + TEST_CLASS(Metrics) + { + }; +} diff --git a/toolsrc/src/triplet.cpp b/toolsrc/src/triplet.cpp new file mode 100644 index 000000000..9ad3d8847 --- /dev/null +++ b/toolsrc/src/triplet.cpp @@ -0,0 +1,59 @@ +#include "triplet.h" +#include "vcpkg_System.h" +#include "vcpkg_Checks.h" + +namespace vcpkg +{ + const triplet triplet::X86_WINDOWS = {"x86-windows"}; + const triplet triplet::X64_WINDOWS = {"x64-windows"}; + const triplet triplet::X86_UWP = {"x86-uwp"}; + const triplet triplet::X64_UWP = {"x64-uwp"}; + const triplet triplet::ARM_UWP = {"arm-uwp"}; + + std::string to_string(const triplet& t) + { + return t.value; + } + + std::string to_printf_arg(const triplet& t) + { + return to_string(t); + } + + bool operator==(const triplet& left, const triplet& right) + { + return left.value == right.value; + } + + bool operator!=(const triplet& left, const triplet& right) + { + return !(left == right); + } + + std::ostream& operator<<(std::ostream& os, const triplet& t) + { + return os << to_string(t); + } + + std::string triplet::architecture() const + { + if (*this == X86_WINDOWS || *this == X86_UWP) + return "x86"; + if (*this == X64_WINDOWS || *this == X64_UWP) + return "x64"; + if (*this == ARM_UWP) + return "arm"; + + Checks::exit_with_message("Unknown architecture: %s", value); + } + + std::string triplet::system() const + { + if (*this == X86_WINDOWS || *this == X64_WINDOWS) + return "windows"; + if (*this == X86_UWP || *this == X64_UWP || *this == ARM_UWP) + return "uwp"; + + Checks::exit_with_message("Unknown system: %s", value); + } +} diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp new file mode 100644 index 000000000..f705858cc --- /dev/null +++ b/toolsrc/src/vcpkg.cpp @@ -0,0 +1,178 @@ +#include "vcpkg.h" +#include <regex> +#include "vcpkg_Files.h" +#include "vcpkglib_helpers.h" + +namespace +{ + using namespace vcpkg; + + struct Parser + { + Parser(const char* c, const char* e) : cur(c), end(e) + { + } + + private: + const char* cur; + const char* const end; + + void peek(char& ch) const + { + if (cur == end) + ch = 0; + else + ch = *cur; + } + + void next(char& ch) + { + if (cur == end) + ch = 0; + else + { + ++cur; + peek(ch); + } + } + + void skip_spaces(char& ch) + { + while (ch == ' ' || ch == '\t') + next(ch); + } + + static bool is_alphanum(char ch) + { + return (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9'); + } + + static bool is_lineend(char ch) + { + return ch == '\r' || ch == '\n' || ch == 0; + } + + void get_fieldvalue(char& ch, std::string& fieldvalue) + { + fieldvalue.clear(); + + auto beginning_of_line = cur; + do + { + // scan to end of current line (it is part of the field value) + while (!is_lineend(ch)) + next(ch); + + fieldvalue.append(beginning_of_line, cur); + + if (ch == '\r') + next(ch); + if (ch == '\n') + next(ch); + + if (is_alphanum(ch)) + { + // Line begins a new field. + return; + } + + beginning_of_line = cur; + + // Line may continue the current field with data or terminate the paragraph, + // depending on first nonspace character. + skip_spaces(ch); + + if (is_lineend(ch)) + { + // Line was whitespace or empty. + // This terminates the field and the paragraph. + // We leave the blank line's whitespace consumed, because it doesn't matter. + return; + } + + // First nonspace is not a newline. This continues the current field value. + // We forcibly convert all newlines into single '\n' for ease of text handling later on. + fieldvalue.push_back('\n'); + } + while (true); + } + + void get_fieldname(char& ch, std::string& fieldname) + { + auto begin_fieldname = cur; + while (is_alphanum(ch) || ch == '-') + next(ch); + Checks::check_throw(ch == ':', "Expected ':'"); + fieldname = std::string(begin_fieldname, cur); + + // skip ': ' + next(ch); + skip_spaces(ch); + } + + void get_paragraph(char& ch, std::unordered_map<std::string, std::string>& fields) + { + fields.clear(); + std::string fieldname; + std::string fieldvalue; + do + { + get_fieldname(ch, fieldname); + + auto it = fields.find(fieldname); + Checks::check_throw(it == fields.end(), "Duplicate field"); + + get_fieldvalue(ch, fieldvalue); + + fields.emplace(fieldname, fieldvalue); + } + while (!is_lineend(ch)); + } + + public: + std::vector<std::unordered_map<std::string, std::string>> get_paragraphs() + { + std::vector<std::unordered_map<std::string, std::string>> paragraphs; + + char ch; + peek(ch); + + while (ch != 0) + { + if (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t') + { + next(ch); + continue; + } + + paragraphs.emplace_back(); + get_paragraph(ch, paragraphs.back()); + } + + return paragraphs; + } + }; +} + +namespace vcpkg +{ + std::string shorten_description(const std::string& desc) + { + auto simple_desc = std::regex_replace(desc.substr(0, 49), std::regex("\\n( |\\t)?"), ""); + if (desc.size() > 49) + simple_desc.append("..."); + return simple_desc; + } + + std::vector<std::unordered_map<std::string, std::string>> get_paragraphs(const fs::path& control_path) + { + return parse_paragraphs(Files::get_contents(control_path).get_or_throw()); + } + + std::vector<std::unordered_map<std::string, std::string>> parse_paragraphs(const std::string& str) + { + return Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); + } +} diff --git a/toolsrc/src/vcpkg_Checks.cpp b/toolsrc/src/vcpkg_Checks.cpp new file mode 100644 index 000000000..d5433b1f5 --- /dev/null +++ b/toolsrc/src/vcpkg_Checks.cpp @@ -0,0 +1,41 @@ +#include "vcpkg_Checks.h" + +#include <stdexcept> +#include "vcpkg_System.h" + +namespace vcpkg {namespace Checks +{ + void unreachable() + { + System::println(System::color::error, "Error: Unreachable code was reached"); + exit(EXIT_FAILURE); + } + + void exit_with_message(const char* errorMessage) + { + System::println(System::color::error, errorMessage); + exit(EXIT_FAILURE); + } + + void throw_with_message(const char* errorMessage) + { + throw std::runtime_error(errorMessage); + } + + void check_throw(bool expression, const char* errorMessage) + { + if (!expression) + { + throw_with_message(errorMessage); + } + } + + void check_exit(bool expression, const char* errorMessage) + { + if (!expression) + { + System::println(System::color::error, errorMessage); + exit(EXIT_FAILURE); + } + } +}} diff --git a/toolsrc/src/vcpkg_Environment.cpp b/toolsrc/src/vcpkg_Environment.cpp new file mode 100644 index 000000000..f70f2b893 --- /dev/null +++ b/toolsrc/src/vcpkg_Environment.cpp @@ -0,0 +1,87 @@ +#include <regex> +#include <array> +#include "vcpkg_Environment.h" +#include "vcpkg_Commands.h" +#include "vcpkg.h" +#include "metrics.h" +#include "vcpkg_System.h" + +namespace vcpkg {namespace Environment +{ + static const fs::path default_cmake_installation_dir = "C:/Program Files/CMake/bin"; + static const fs::path default_cmake_installation_dir_x86 = "C:/Program Files (x86)/CMake/bin"; + static const fs::path default_git_installation_dir = "C:/Program Files/git/cmd"; + static const fs::path default_git_installation_dir_x86 = "C:/Program Files (x86)/git/cmd"; + + static void ensure_on_path(const std::array<int, 3>& version, const wchar_t* version_check_cmd, const wchar_t* install_cmd) + { + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(version_check_cmd); + if (ec_data.exit_code == 0) + { + // version check + std::regex re(R"###((\d+)\.(\d+)\.(\d+))###"); + std::match_results<std::string::const_iterator> match; + auto found = std::regex_search(ec_data.output, match, re); + if (found) + { + int d1 = atoi(match[1].str().c_str()); + int d2 = atoi(match[2].str().c_str()); + int d3 = atoi(match[3].str().c_str()); + if (d1 > version[0] || (d1 == version[0] && d2 > version[1]) || (d1 == version[0] && d2 == version[1] && d3 >= version[2])) + { + // satisfactory version found + return; + } + } + } + + auto rc = System::cmd_execute(install_cmd); + if (rc) + { + System::println(System::color::error, "Launching powershell failed or was denied"); + TrackProperty("error", "powershell install failed"); + TrackProperty("installcmd", install_cmd); + exit(rc); + } + } + + void ensure_git_on_path(const vcpkg_paths& paths) + { + const fs::path downloaded_git = paths.downloads / "PortableGit" / "cmd"; + const std::wstring path_buf = Strings::format(L"%s;%s;%s;%s", + downloaded_git.native(), + System::wdupenv_str(L"PATH"), + default_git_installation_dir.native(), + default_git_installation_dir_x86.native()); + _wputenv_s(L"PATH", path_buf.c_str()); + + static constexpr std::array<int, 3> git_version = {2,0,0}; + // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned + ensure_on_path(git_version, L"git --version 2>&1", L"powershell -ExecutionPolicy Bypass scripts\\fetchDependency.ps1 -Dependency git"); + } + + void ensure_cmake_on_path(const vcpkg_paths& paths) + { + const fs::path downloaded_cmake = paths.downloads / "cmake-3.5.2-win32-x86" / "bin"; + const std::wstring path_buf = Strings::format(L"%s;%s;%s;%s", + downloaded_cmake.native(), + System::wdupenv_str(L"PATH"), + default_cmake_installation_dir.native(), + default_cmake_installation_dir_x86.native()); + _wputenv_s(L"PATH", path_buf.c_str()); + + static constexpr std::array<int, 3> cmake_version = {3,5,0}; + // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned + ensure_on_path(cmake_version, L"cmake --version 2>&1", L"powershell -ExecutionPolicy Bypass scripts\\fetchDependency.ps1 -Dependency cmake"); + } + + void ensure_nuget_on_path(const vcpkg_paths& paths) + { + const std::wstring path_buf = Strings::format(L"%s;%s", paths.downloads.native(), System::wdupenv_str(L"PATH")); + _wputenv_s(L"PATH", path_buf.c_str()); + + static constexpr std::array<int, 3> nuget_version = {1,0,0}; + // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned + ensure_on_path(nuget_version, L"nuget 2>&1", L"powershell -ExecutionPolicy Bypass scripts\\fetchDependency.ps1 -Dependency nuget"); + } +}} diff --git a/toolsrc/src/vcpkg_Files.cpp b/toolsrc/src/vcpkg_Files.cpp new file mode 100644 index 000000000..49a661157 --- /dev/null +++ b/toolsrc/src/vcpkg_Files.cpp @@ -0,0 +1,38 @@ +#include "vcpkg_Files.h" +#include <fstream> +#include <filesystem> + +namespace fs = std::tr2::sys; + +namespace vcpkg {namespace Files +{ + void check_is_directory(const fs::path& dirpath) + { + Checks::check_throw(fs::is_directory(dirpath), "The path %s is not a directory", dirpath.string()); + } + + expected<std::string> get_contents(const fs::path& file_path) noexcept + { + std::fstream file_stream(file_path, std::ios_base::in | std::ios_base::binary); + if (file_stream.fail()) + { + return std::errc::no_such_file_or_directory; + } + + file_stream.seekg(0, file_stream.end); + auto length = file_stream.tellg(); + file_stream.seekg(0, file_stream.beg); + + if (length > SIZE_MAX) + { + return std::errc::file_too_large; + } + + std::string output; + output.resize(static_cast<size_t>(length)); + file_stream.read(&output[0], length); + file_stream.close(); + + return std::move(output); + } +}} diff --git a/toolsrc/src/vcpkg_Strings.cpp b/toolsrc/src/vcpkg_Strings.cpp new file mode 100644 index 000000000..b0312536a --- /dev/null +++ b/toolsrc/src/vcpkg_Strings.cpp @@ -0,0 +1,60 @@ +#include "vcpkg_Strings.h" + +#include <cstdarg> +#include <algorithm> +#include <codecvt> +#include <iterator> + +namespace vcpkg {namespace Strings {namespace details +{ + std::string format_internal(const char* fmtstr, ...) + { + va_list lst; + va_start(lst, fmtstr); + + auto sz = _vscprintf(fmtstr, lst); + std::string output(sz, '\0'); + _vsnprintf_s(&output[0], output.size() + 1, output.size() + 1, fmtstr, lst); + va_end(lst); + + return output; + } + + std::wstring format_internal(const wchar_t* fmtstr, ...) + { + va_list lst; + va_start(lst, fmtstr); + + auto sz = _vscwprintf(fmtstr, lst); + std::wstring output(sz, '\0'); + _vsnwprintf_s(&output[0], output.size() + 1, output.size() + 1, fmtstr, lst); + va_end(lst); + + return output; + } +}}} + +namespace vcpkg {namespace Strings +{ + std::wstring utf8_to_utf16(const std::string& s) + { + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion; + return conversion.from_bytes(s); + } + + std::string utf16_to_utf8(const std::wstring& w) + { + std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion; + return conversion.to_bytes(w); + } + + std::string::const_iterator case_insensitive_find(const std::string& s, const std::string& pattern) + { + std::string patter_as_lower_case; + std::transform(pattern.begin(), pattern.end(), back_inserter(patter_as_lower_case), tolower); + return search(s.begin(), s.end(), patter_as_lower_case.begin(), patter_as_lower_case.end(), [](const char a, const char b) + { + return (tolower(a) == b); + }); + } +}} diff --git a/toolsrc/src/vcpkg_System.cpp b/toolsrc/src/vcpkg_System.cpp new file mode 100644 index 000000000..71b4087d2 --- /dev/null +++ b/toolsrc/src/vcpkg_System.cpp @@ -0,0 +1,111 @@ +#include "vcpkg_System.h" +#include <iostream> +#include <Windows.h> +#include <regex> + +namespace fs = std::tr2::sys; + +namespace vcpkg {namespace System +{ + fs::path get_exe_path_of_current_process() + { + wchar_t buf[_MAX_PATH ]; + int bytes = GetModuleFileNameW(nullptr, buf, _MAX_PATH); + if (bytes == 0) + std::abort(); + return fs::path(buf, buf + bytes); + } + + int cmd_execute(const wchar_t* cmd_line) + { + // Basically we are wrapping it in quotes + const std::wstring& actual_cmd_line = Strings::format(LR"###("%s")###", cmd_line); + int exit_code = _wsystem(actual_cmd_line.c_str()); + return exit_code; + } + + exit_code_and_output cmd_execute_and_capture_output(const wchar_t* cmd_line) + { + const std::wstring& actual_cmd_line = Strings::format(LR"###("%s")###", cmd_line); + + std::string output; + char buf[1024]; + auto pipe = _wpopen(actual_cmd_line.c_str(), L"r"); + if (pipe == nullptr) + { + return {1, output}; + } + while (fgets(buf, 1024, pipe)) + { + output.append(buf); + } + if (!feof(pipe)) + { + return {1, output}; + } + auto ec = _pclose(pipe); + return {ec, output}; + } + + void print(const char* message) + { + std::cout << message; + } + + void println(const char* message) + { + print(message); + std::cout << "\n"; + } + + void print(color c, const char* message) + { + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + + CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo{}; + GetConsoleScreenBufferInfo(hConsole, &consoleScreenBufferInfo); + auto original_color = consoleScreenBufferInfo.wAttributes; + + SetConsoleTextAttribute(hConsole, static_cast<int>(c) | (original_color & 0xF0)); + std::cout << message; + SetConsoleTextAttribute(hConsole, original_color); + } + + void println(color c, const char* message) + { + print(c, message); + std::cout << "\n"; + } + + std::wstring wdupenv_str(const wchar_t* varname) noexcept + { + std::wstring ret; + wchar_t* buffer; + _wdupenv_s(&buffer, nullptr, varname); + if (buffer != nullptr) + { + ret = buffer; + free(buffer); + } + return ret; + } + + void Stopwatch::start() + { + static_assert(sizeof(start_time) == sizeof(LARGE_INTEGER), ""); + + QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&start_time)); + } + + void Stopwatch::stop() + { + QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER*>(&end_time)); + QueryPerformanceFrequency(reinterpret_cast<LARGE_INTEGER*>(&freq)); + } + + double Stopwatch::microseconds() const + { + return (reinterpret_cast<const LARGE_INTEGER*>(&end_time)->QuadPart - + reinterpret_cast<const LARGE_INTEGER*>(&start_time)->QuadPart) * 1000000.0 / reinterpret_cast<const LARGE_INTEGER*>(&freq)->QuadPart; + } +}} diff --git a/toolsrc/src/vcpkg_cmd_arguments.cpp b/toolsrc/src/vcpkg_cmd_arguments.cpp new file mode 100644 index 000000000..4cfc12716 --- /dev/null +++ b/toolsrc/src/vcpkg_cmd_arguments.cpp @@ -0,0 +1,255 @@ +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> +#include "vcpkg_cmd_arguments.h" +#include "vcpkg_Commands.h" +#include "vcpkg_Graphs.h" +#include <unordered_set> +#include "metrics.h" +#include "vcpkg.h" +#include "vcpkg_System.h" + +namespace vcpkg +{ + static void parse_value( + const std::string* arg_begin, + const std::string* arg_end, + const std::string& option_name, + std::unique_ptr<std::string>& option_field) + { + if (arg_begin == arg_end) + { + System::println(System::color::error, "Error: expected value after %s", option_name); + TrackProperty("error", "error option name"); + print_usage(); + exit(EXIT_FAILURE); + } + + if (option_field != nullptr) + { + System::println(System::color::error, "Error: %s specified multiple times", option_name); + TrackProperty("error", "error option specified multiple times"); + print_usage(); + exit(EXIT_FAILURE); + } + + option_field = std::make_unique<std::string>(*arg_begin); + } + + static void parse_switch( + opt_bool new_setting, + const std::string& option_name, + opt_bool& option_field) + { + if (option_field != opt_bool::unspecified && option_field != new_setting) + { + System::println(System::color::error, "Error: conflicting values specified for --%s", option_name); + TrackProperty("error", "error conflicting switches"); + print_usage(); + exit(EXIT_FAILURE); + } + option_field = new_setting; + } + + vcpkg_cmd_arguments vcpkg_cmd_arguments::create_from_command_line(const int argc, const wchar_t* const* const argv) + { + std::vector<std::string> v; + for (int i = 1; i < argc; ++i) + { + v.push_back(Strings::utf16_to_utf8(argv[i])); + } + + return vcpkg_cmd_arguments::create_from_arg_sequence(v.data(), v.data() + v.size()); + } + + vcpkg_cmd_arguments vcpkg_cmd_arguments::create_from_arg_sequence(const std::string* arg_begin, const std::string* arg_end) + { + vcpkg_cmd_arguments args; + + for (; arg_begin != arg_end; ++arg_begin) + { + std::string arg = *arg_begin; + + if (arg.empty()) + { + continue; + } + + if (arg[0] == '-' && arg[1] != '-') + { + System::println(System::color::error, "Error: short options are not supported: %s", arg); + TrackProperty("error", "error short options are not supported"); + exit(EXIT_FAILURE); + } + + if (arg[0] == '-' && arg[1] == '-') + { + // command switch + if (arg == "--vcpkg-root") + { + ++arg_begin; + parse_value(arg_begin, arg_end, "--vcpkg-root", args.vcpkg_root_dir); + continue; + } + if (arg == "--triplet") + { + ++arg_begin; + parse_value(arg_begin, arg_end, "--triplet", args.target_triplet); + continue; + } + if (arg == "--debug") + { + parse_switch(opt_bool::enabled, "debug", args.debug); + continue; + } + if (arg == "--sendmetrics") + { + parse_switch(opt_bool::enabled, "sendmetrics", args.sendmetrics); + continue; + } + if (arg == "--printmetrics") + { + parse_switch(opt_bool::enabled, "printmetrics", args.printmetrics); + continue; + } + if (arg == "--no-sendmetrics") + { + parse_switch(opt_bool::disabled, "sendmetrics", args.sendmetrics); + continue; + } + if (arg == "--no-printmetrics") + { + parse_switch(opt_bool::disabled, "printmetrics", args.printmetrics); + continue; + } + + args.optional_command_arguments.insert(arg); + continue; + } + + if (args.command.empty()) + { + args.command = arg; + } + else + { + args.command_arguments.push_back(arg); + } + } + + return args; + } + + std::unordered_set<std::string> vcpkg_cmd_arguments::check_and_get_optional_command_arguments(const std::vector<std::string>& valid_options) const + { + std::unordered_set<std::string> output; + auto options_copy = this->optional_command_arguments; + for (const std::string& option : valid_options) + { + auto it = options_copy.find(option); + if (it != options_copy.end()) + { + output.insert(option); + options_copy.erase(it); + } + } + + if (!options_copy.empty()) + { + System::println(System::color::error, "Unknown option(s) for command '%s':", this->command); + for (const std::string& option : options_copy) + { + System::println(option.c_str()); + } + exit(EXIT_FAILURE); + } + + return output; + } + + void vcpkg_cmd_arguments::check_max_args(size_t arg_count, const char* example_text) const + { + if (command_arguments.size() > arg_count) + { + System::println(System::color::error, "Error: too many arguments to command %s", command); + if (example_text != nullptr) + print_example(example_text); + else + print_usage(); + exit(EXIT_FAILURE); + } + } + + std::vector<package_spec> vcpkg_cmd_arguments::extract_package_specs_with_unmet_dependencies(const vcpkg_paths& paths, const triplet& default_target_triplet, const StatusParagraphs& status_db) const + { + std::vector<package_spec> specs = parse_all_arguments_as_package_specs(default_target_triplet); + std::unordered_set<package_spec> had_its_immediate_dependencies_added; + Graphs::Graph<package_spec> graph; + graph.add_vertices(specs); + + while (!specs.empty()) + { + package_spec spec = specs.back(); + specs.pop_back(); + + if (had_its_immediate_dependencies_added.find(spec) != had_its_immediate_dependencies_added.end()) + { + continue; + } + + std::vector<std::string> dependencies_as_string = get_unmet_package_dependencies(paths, spec, status_db); + + for (const std::string& dep_as_string : dependencies_as_string) + { + package_spec current_dep = {dep_as_string, spec.target_triplet}; + auto it = status_db.find(current_dep.name, current_dep.target_triplet); + if (it != status_db.end() && (*it)->want == want_t::install) + { + continue; + } + + graph.add_edge(spec, current_dep); + if (had_its_immediate_dependencies_added.find(current_dep) == had_its_immediate_dependencies_added.end()) + { + specs.push_back(std::move(current_dep)); + } + } + + had_its_immediate_dependencies_added.insert(spec); + } + + return graph.find_topological_sort(); + } + + std::vector<package_spec> vcpkg_cmd_arguments::parse_all_arguments_as_package_specs(const triplet& default_target_triplet, const char* example_text) const + { + size_t arg_count = command_arguments.size(); + if (arg_count < 1) + { + System::println(System::color::error, "Error: %s requires one or more package specifiers", this->command); + if (example_text == nullptr) + print_example(Strings::format("%s zlib zlib:x64-windows curl boost", this->command).c_str()); + else + print_example(example_text); + exit(EXIT_FAILURE); + } + std::vector<package_spec> specs; + specs.reserve(arg_count); + + for (const std::string& command_argument : command_arguments) + { + expected<package_spec> current_spec = vcpkg::parse(command_argument, default_target_triplet); + if (auto spec = current_spec.get()) + { + specs.push_back(std::move(*spec)); + } + else + { + System::println(System::color::error, "Error: %s: %s", current_spec.error_code().message(), command_argument); + print_example(Strings::format("%s zlib:x64-windows", this->command).c_str()); + exit(EXIT_FAILURE); + } + } + + return specs; + } +} diff --git a/toolsrc/src/vcpkg_metrics_uploader.cpp b/toolsrc/src/vcpkg_metrics_uploader.cpp new file mode 100644 index 000000000..f1f4a52ed --- /dev/null +++ b/toolsrc/src/vcpkg_metrics_uploader.cpp @@ -0,0 +1,25 @@ +#include "metrics.h" +#include <filesystem> +#include "vcpkg_Checks.h" +#include "vcpkg_Files.h" +#include <Windows.h> + +namespace fs = std::tr2::sys; +using namespace vcpkg; + +int WINAPI +WinMain( + _In_ HINSTANCE hInstance, + _In_opt_ HINSTANCE hPrevInstance, + _In_ LPSTR lpCmdLine, + _In_ int nShowCmd +) +{ + LPWSTR* szArgList; + int argCount; + + szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount); + + Checks::check_exit(argCount == 2, "Requires exactly one argument, the path to the payload file"); + Upload(Files::get_contents(szArgList[1]).get_or_throw()); +} diff --git a/toolsrc/src/vcpkg_paths.cpp b/toolsrc/src/vcpkg_paths.cpp new file mode 100644 index 000000000..10b6d992a --- /dev/null +++ b/toolsrc/src/vcpkg_paths.cpp @@ -0,0 +1,48 @@ +#include <filesystem> +#include "expected.h" +#include "vcpkg_paths.h" +#include "metrics.h" +#include "vcpkg_System.h" + +namespace fs = std::tr2::sys; + +namespace vcpkg +{ + expected<vcpkg_paths> vcpkg_paths::create(const fs::path& vcpkg_root_dir) + { + std::error_code ec; + const fs::path canonical_vcpkg_root_dir = fs::canonical(vcpkg_root_dir, ec); + if (ec) + { + return ec; + } + + vcpkg_paths paths; + paths.root = canonical_vcpkg_root_dir; + + if (paths.root.empty()) + { + System::println(System::color::error, "Invalid vcpkg root directory: %s", paths.root.string()); + TrackProperty("error", "Invalid vcpkg root directory"); + exit(EXIT_FAILURE); + } + + paths.packages = paths.root / "packages"; + paths.buildtrees = paths.root / "buildtrees"; + paths.downloads = paths.root / "downloads"; + paths.ports = paths.root / "ports"; + paths.installed = paths.root / "installed"; + paths.triplets = paths.root / "triplets"; + + paths.buildsystems = paths.root / "scripts" / "buildsystems"; + paths.buildsystems_msbuild_targets = paths.buildsystems / "msbuild" / "vcpkg.targets"; + + paths.vcpkg_dir = paths.installed / "vcpkg"; + paths.vcpkg_dir_status_file = paths.vcpkg_dir / "status"; + paths.vcpkg_dir_info = paths.vcpkg_dir / "info"; + paths.vcpkg_dir_updates = paths.vcpkg_dir / "updates"; + + paths.ports_cmake = paths.root / "scripts" / "ports.cmake"; + return paths; + } +} diff --git a/toolsrc/src/vcpkg_version.cpp b/toolsrc/src/vcpkg_version.cpp new file mode 100644 index 000000000..da52b7cab --- /dev/null +++ b/toolsrc/src/vcpkg_version.cpp @@ -0,0 +1,25 @@ +#include "vcpkg.h" +#include "metrics.h" + +#define STRINGIFY(X) #X +#define MACRO_TO_STRING(X) STRINGIFY(X) + +#define VCPKG_VERSION_AS_STRING MACRO_TO_STRING(VCPKG_VERSION)"" // Double quotes needed at the end to prevent blank token + +const std::string& vcpkg::version() +{ + static const std::string s_version = +#include "../VERSION.txt" + + +#pragma warning( push ) +#pragma warning( disable : 4003) + // VCPKG_VERSION can be defined but have no value, which yields C4003. + + std::string(VCPKG_VERSION_AS_STRING) +#pragma warning( pop ) +#ifndef NDEBUG + + std::string("-debug") +#endif + + std::string(GetCompiledMetricsEnabled() ? "" : "-external"); + return s_version; +} diff --git a/toolsrc/src/vcpkglib_helpers.cpp b/toolsrc/src/vcpkglib_helpers.cpp new file mode 100644 index 000000000..e947dc647 --- /dev/null +++ b/toolsrc/src/vcpkglib_helpers.cpp @@ -0,0 +1,50 @@ +#include "vcpkg_Checks.h" +#include "vcpkglib_helpers.h" +#include <unordered_map> + +namespace vcpkg {namespace details +{ + void optional_field(const std::unordered_map<std::string, std::string>& fields, std::string& out, const std::string& fieldname) + { + auto it = fields.find(fieldname); + if (it == fields.end()) + { + out.clear(); + } + + else + { + out = it->second; + } + }; + + void required_field(const std::unordered_map<std::string, std::string>& fields, std::string& out, const std::string& fieldname) + { + auto it = fields.find(fieldname); + vcpkg::Checks::check_throw(it != fields.end(), "Required field not present: %s", fieldname); + out = it->second; + }; + + void parse_depends(const std::string& depends_string, std::vector<std::string>& out) + { + size_t cur = 0; + do + { + auto pos = depends_string.find(',', cur); + if (pos == std::string::npos) + { + out.push_back(depends_string.substr(cur)); + return; + } + out.push_back(depends_string.substr(cur, pos - cur)); + + // skip comma and space + ++pos; + if (depends_string[pos] == ' ') + ++pos; + + cur = pos; + } + while (cur != std::string::npos); + } +}} |
