diff options
| author | Alexander Saprykin <xelfium@gmail.com> | 2018-05-26 13:27:14 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-05-26 13:27:14 +0200 |
| commit | 4ce5f064282c3a8d8d710aa82af7aa346b0c6dd5 (patch) | |
| tree | d95c9490352eb73f078d34a33bc4bb44ac9fa48b /toolsrc/src | |
| parent | fb689bd13dd6ba563a885d71fff1dd2b32a615db (diff) | |
| parent | 2ac7527b40b1dbeb7856b9f763362c1e139e2ca9 (diff) | |
| download | vcpkg-4ce5f064282c3a8d8d710aa82af7aa346b0c6dd5.tar.gz vcpkg-4ce5f064282c3a8d8d710aa82af7aa346b0c6dd5.zip | |
Merge pull request #1 from Microsoft/master
Update vcpkg from upstream
Diffstat (limited to 'toolsrc/src')
109 files changed, 12679 insertions, 5504 deletions
diff --git a/toolsrc/src/BinaryParagraph.cpp b/toolsrc/src/BinaryParagraph.cpp deleted file mode 100644 index d545eee2a..000000000 --- a/toolsrc/src/BinaryParagraph.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "pch.h" - -#include "BinaryParagraph.h" -#include "vcpkg_Checks.h" -#include "vcpkglib_helpers.h" - -using namespace vcpkg::details; - -namespace vcpkg -{ - namespace BinaryParagraphRequiredField - { - static const std::string PACKAGE = "Package"; - static const std::string VERSION = "Version"; - static const std::string ARCHITECTURE = "Architecture"; - static const std::string MULTI_ARCH = "Multi-Arch"; - } - - namespace BinaryParagraphOptionalField - { - static const std::string DESCRIPTION = "Description"; - static const std::string MAINTAINER = "Maintainer"; - static const std::string DEPENDS = "Depends"; - } - - BinaryParagraph::BinaryParagraph() = default; - - BinaryParagraph::BinaryParagraph(std::unordered_map<std::string, std::string> fields) - { - const std::string name = details::remove_required_field(&fields, BinaryParagraphRequiredField::PACKAGE); - const std::string architecture = - details::remove_required_field(&fields, BinaryParagraphRequiredField::ARCHITECTURE); - const Triplet triplet = Triplet::from_canonical_name(architecture); - - this->spec = PackageSpec::from_name_and_triplet(name, triplet).value_or_exit(VCPKG_LINE_INFO); - this->version = details::remove_required_field(&fields, BinaryParagraphRequiredField::VERSION); - - this->description = details::remove_optional_field(&fields, BinaryParagraphOptionalField::DESCRIPTION); - this->maintainer = details::remove_optional_field(&fields, BinaryParagraphOptionalField::MAINTAINER); - - std::string multi_arch = details::remove_required_field(&fields, BinaryParagraphRequiredField::MULTI_ARCH); - Checks::check_exit(VCPKG_LINE_INFO, multi_arch == "same", "Multi-Arch must be 'same' but was %s", multi_arch); - - std::string deps = details::remove_optional_field(&fields, BinaryParagraphOptionalField::DEPENDS); - this->depends = parse_depends(deps); - } - - BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const Triplet& triplet) - { - this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); - this->version = spgh.version; - this->description = spgh.description; - this->maintainer = spgh.maintainer; - this->depends = filter_dependencies(spgh.depends, triplet); - } - - std::string BinaryParagraph::displayname() const { return this->spec.to_string(); } - - std::string BinaryParagraph::dir() const { return this->spec.dir(); } - - std::string BinaryParagraph::fullstem() const - { - return Strings::format("%s_%s_%s", this->spec.name(), this->version, this->spec.triplet()); - } - - void serialize(const BinaryParagraph& pgh, std::string& out_str) - { - out_str.append("Package: ").append(pgh.spec.name()).push_back('\n'); - out_str.append("Version: ").append(pgh.version).push_back('\n'); - if (!pgh.depends.empty()) - { - out_str.append("Depends: "); - out_str.append(Strings::join(", ", pgh.depends)); - out_str.push_back('\n'); - } - - out_str.append("Architecture: ").append(pgh.spec.triplet().to_string()).push_back('\n'); - out_str.append("Multi-Arch: same\n"); - - if (!pgh.maintainer.empty()) out_str.append("Maintainer: ").append(pgh.maintainer).push_back('\n'); - if (!pgh.description.empty()) out_str.append("Description: ").append(pgh.description).push_back('\n'); - } -} diff --git a/toolsrc/src/PackageSpec.cpp b/toolsrc/src/PackageSpec.cpp deleted file mode 100644 index 69883a030..000000000 --- a/toolsrc/src/PackageSpec.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include "pch.h" - -#include "PackageSpec.h" -#include "vcpkg_Util.h" - -namespace vcpkg -{ - static bool is_valid_package_spec_char(char c) { return (c == '-') || isdigit(c) || (isalpha(c) && islower(c)); } - - Expected<PackageSpec> PackageSpec::from_string(const std::string& spec_as_string, const Triplet& default_triplet) - { - auto pos = spec_as_string.find(':'); - if (pos == std::string::npos) - { - return from_name_and_triplet(spec_as_string, default_triplet); - } - - auto pos2 = spec_as_string.find(':', pos + 1); - if (pos2 != std::string::npos) - { - return std::error_code(PackageSpecParseResult::TOO_MANY_COLONS); - } - - const std::string name = spec_as_string.substr(0, pos); - const Triplet triplet = Triplet::from_canonical_name(spec_as_string.substr(pos + 1)); - return from_name_and_triplet(name, triplet); - } - - Expected<PackageSpec> PackageSpec::from_name_and_triplet(const std::string& name, const Triplet& triplet) - { - if (Util::find_if_not(name, is_valid_package_spec_char) != name.end()) - { - return std::error_code(PackageSpecParseResult::INVALID_CHARACTERS); - } - - PackageSpec p; - p.m_name = name; - p.m_triplet = triplet; - return p; - } - - const std::string& PackageSpec::name() const { return this->m_name; } - - const Triplet& PackageSpec::triplet() const { return this->m_triplet; } - - std::string PackageSpec::dir() const { return Strings::format("%s_%s", this->m_name, this->m_triplet); } - - std::string PackageSpec::to_string(const std::string& name, const Triplet& triplet) - { - return Strings::format("%s:%s", name, triplet); - } - std::string PackageSpec::to_string() const { return to_string(this->name(), this->triplet()); } - - bool operator==(const PackageSpec& left, const PackageSpec& right) - { - return left.name() == right.name() && left.triplet() == right.triplet(); - } - - bool operator!=(const PackageSpec& left, const PackageSpec& right) { return !(left == right); } -} diff --git a/toolsrc/src/PackageSpecParseResult.cpp b/toolsrc/src/PackageSpecParseResult.cpp deleted file mode 100644 index 487c3aa1d..000000000 --- a/toolsrc/src/PackageSpecParseResult.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include "pch.h" - -#include "PackageSpecParseResult.h" -#include "vcpkg_Checks.h" - -namespace vcpkg -{ - const char* PackageSpecParseResultCategoryImpl::name() const noexcept { return "PackageSpecParseResult"; } - - std::string PackageSpecParseResultCategoryImpl::message(int ev) const noexcept - { - switch (static_cast<PackageSpecParseResult>(ev)) - { - case PackageSpecParseResult::SUCCESS: return "OK"; - case PackageSpecParseResult::TOO_MANY_COLONS: return "Too many colons"; - case PackageSpecParseResult::INVALID_CHARACTERS: - return "Contains invalid characters. Only alphanumeric lowercase ASCII characters and dashes are " - "allowed"; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - - const std::error_category& package_spec_parse_result_category() - { - static PackageSpecParseResultCategoryImpl instance; - return instance; - } - - std::error_code make_error_code(PackageSpecParseResult e) - { - return std::error_code(static_cast<int>(e), package_spec_parse_result_category()); - } - - PackageSpecParseResult to_package_spec_parse_result(int i) { return static_cast<PackageSpecParseResult>(i); } - - PackageSpecParseResult to_package_spec_parse_result(std::error_code ec) - { - return to_package_spec_parse_result(ec.value()); - } -} diff --git a/toolsrc/src/PostBuildLint_BuildPolicies.cpp b/toolsrc/src/PostBuildLint_BuildPolicies.cpp deleted file mode 100644 index 001ba31e7..000000000 --- a/toolsrc/src/PostBuildLint_BuildPolicies.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include "pch.h" - -#include "PostBuildLint_BuildPolicies.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Enums.h" - -namespace vcpkg::PostBuildLint -{ - static const std::string NULLVALUE_STRING = Enums::nullvalue_to_string(BuildPoliciesC::ENUM_NAME); - - static const std::string NAME_EMPTY_PACKAGE = "PolicyEmptyPackage"; - static const std::string NAME_DLLS_WITHOUT_LIBS = "PolicyDLLsWithoutLIBs"; - static const std::string NAME_ONLY_RELEASE_CRT = "PolicyOnlyReleaseCRT"; - static const std::string NAME_EMPTY_INCLUDE_FOLDER = "PolicyEmptyIncludeFolder"; - - BuildPolicies BuildPolicies::parse(const std::string& s) - { - if (s == NAME_EMPTY_PACKAGE) - { - return BuildPoliciesC::EMPTY_PACKAGE; - } - - if (s == NAME_DLLS_WITHOUT_LIBS) - { - return BuildPoliciesC::DLLS_WITHOUT_LIBS; - } - - if (s == NAME_ONLY_RELEASE_CRT) - { - return BuildPoliciesC::ONLY_RELEASE_CRT; - } - - if (s == NAME_EMPTY_INCLUDE_FOLDER) - { - return BuildPoliciesC::EMPTY_INCLUDE_FOLDER; - } - - return BuildPoliciesC::NULLVALUE; - } - - const std::string& BuildPolicies::to_string() const - { - switch (this->backing_enum) - { - case BuildPoliciesC::EMPTY_PACKAGE: return NAME_EMPTY_PACKAGE; - case BuildPoliciesC::DLLS_WITHOUT_LIBS: return NAME_DLLS_WITHOUT_LIBS; - case BuildPoliciesC::ONLY_RELEASE_CRT: return NAME_ONLY_RELEASE_CRT; - case BuildPoliciesC::EMPTY_INCLUDE_FOLDER: return NAME_EMPTY_INCLUDE_FOLDER; - case BuildPoliciesC::NULLVALUE: return NULLVALUE_STRING; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - - const std::string& BuildPolicies::cmake_variable() const - { - static const std::string CMAKE_VARIABLE_EMPTY_PACKAGE = "VCPKG_POLICY_EMPTY_PACKAGE"; - static const std::string CMAKE_VARIABLE_DLLS_WITHOUT_LIBS = "VCPKG_POLICY_DLLS_WITHOUT_LIBS"; - static const std::string CMAKE_VARIABLE_ONLY_RELEASE_CRT = "VCPKG_POLICY_ONLY_RELEASE_CRT"; - static const std::string CMAKE_VARIABLE_EMPTY_INCLUDE_FOLDER = "VCPKG_POLICY_EMPTY_INCLUDE_FOLDER"; - - switch (this->backing_enum) - { - case BuildPoliciesC::EMPTY_PACKAGE: return CMAKE_VARIABLE_EMPTY_PACKAGE; - case BuildPoliciesC::DLLS_WITHOUT_LIBS: return CMAKE_VARIABLE_DLLS_WITHOUT_LIBS; - case BuildPoliciesC::ONLY_RELEASE_CRT: return CMAKE_VARIABLE_ONLY_RELEASE_CRT; - case BuildPoliciesC::EMPTY_INCLUDE_FOLDER: return CMAKE_VARIABLE_EMPTY_INCLUDE_FOLDER; - case BuildPoliciesC::NULLVALUE: Enums::nullvalue_used(VCPKG_LINE_INFO, BuildPoliciesC::ENUM_NAME); - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } -} diff --git a/toolsrc/src/PostBuildLint_ConfigurationType.cpp b/toolsrc/src/PostBuildLint_ConfigurationType.cpp deleted file mode 100644 index eeccb1804..000000000 --- a/toolsrc/src/PostBuildLint_ConfigurationType.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "pch.h" - -#include "PackageSpec.h" -#include "PostBuildLint_ConfigurationType.h" -#include "vcpkg_Enums.h" - -namespace vcpkg::PostBuildLint -{ - static const std::string NULLVALUE_STRING = Enums::nullvalue_to_string(ConfigurationTypeC::ENUM_NAME); - - static const std::string NAME_DEBUG = "Debug"; - static const std::string NAME_RELEASE = "Release"; - - const std::string& ConfigurationType::to_string() const - { - switch (this->backing_enum) - { - case ConfigurationTypeC::DEBUG: return NAME_DEBUG; - case ConfigurationTypeC::RELEASE: return NAME_RELEASE; - case ConfigurationTypeC::NULLVALUE: return NULLVALUE_STRING; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } -} diff --git a/toolsrc/src/PostBuildLint_LinkageType.cpp b/toolsrc/src/PostBuildLint_LinkageType.cpp deleted file mode 100644 index 43bdbed7b..000000000 --- a/toolsrc/src/PostBuildLint_LinkageType.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "pch.h" - -#include "PostBuildLint_LinkageType.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Enums.h" - -namespace vcpkg::PostBuildLint -{ - static const std::string NULLVALUE_STRING = Enums::nullvalue_to_string(LinkageTypeC::ENUM_NAME); - - static const std::string NAME_DYNAMIC = "dynamic"; - static const std::string NAME_STATIC = "static"; - - LinkageType LinkageType::value_of(const std::string& as_string) - { - if (as_string == NAME_DYNAMIC) - { - return LinkageTypeC::DYNAMIC; - } - - if (as_string == NAME_STATIC) - { - return LinkageTypeC::STATIC; - } - - return LinkageTypeC::NULLVALUE; - } - - const std::string& LinkageType::to_string() const - { - switch (this->backing_enum) - { - case LinkageTypeC::DYNAMIC: return NAME_DYNAMIC; - case LinkageTypeC::STATIC: return NAME_STATIC; - case LinkageTypeC::NULLVALUE: return NULLVALUE_STRING; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } -} diff --git a/toolsrc/src/SourceParagraph.cpp b/toolsrc/src/SourceParagraph.cpp deleted file mode 100644 index d140ce72b..000000000 --- a/toolsrc/src/SourceParagraph.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "pch.h" - -#include "SourceParagraph.h" -#include "Triplet.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Maps.h" -#include "vcpkg_System.h" -#include "vcpkglib_helpers.h" - -namespace vcpkg -{ - // - namespace SourceParagraphRequiredField - { - static const std::string SOURCE = "Source"; - static const std::string VERSION = "Version"; - } - - namespace SourceParagraphOptionalField - { - static const std::string DESCRIPTION = "Description"; - static const std::string MAINTAINER = "Maintainer"; - static const std::string BUILD_DEPENDS = "Build-Depends"; - } - - static const std::vector<std::string>& get_list_of_valid_fields() - { - static const std::vector<std::string> valid_fields = {SourceParagraphRequiredField::SOURCE, - SourceParagraphRequiredField::VERSION, - - SourceParagraphOptionalField::DESCRIPTION, - SourceParagraphOptionalField::MAINTAINER, - SourceParagraphOptionalField::BUILD_DEPENDS}; - - return valid_fields; - } - - SourceParagraph::SourceParagraph() = default; - - SourceParagraph::SourceParagraph(std::unordered_map<std::string, std::string> fields) - { - this->name = details::remove_required_field(&fields, SourceParagraphRequiredField::SOURCE); - this->version = details::remove_required_field(&fields, SourceParagraphRequiredField::VERSION); - this->description = details::remove_optional_field(&fields, SourceParagraphOptionalField::DESCRIPTION); - this->maintainer = details::remove_optional_field(&fields, SourceParagraphOptionalField::MAINTAINER); - - std::string deps = details::remove_optional_field(&fields, SourceParagraphOptionalField::BUILD_DEPENDS); - this->depends = expand_qualified_dependencies(parse_depends(deps)); - - if (!fields.empty()) - { - const std::vector<std::string> remaining_fields = Maps::extract_keys(fields); - const std::vector<std::string>& valid_fields = get_list_of_valid_fields(); - - const std::string remaining_fields_as_string = Strings::join("\n ", remaining_fields); - const std::string valid_fields_as_string = Strings::join("\n ", valid_fields); - - System::println( - System::Color::error, "Error: There are invalid fields in the Source Paragraph of %s", this->name); - System::println("The following fields were not expected:\n\n %s\n\n", remaining_fields_as_string); - System::println("This is the list of valid fields (case-sensitive): \n\n %s\n", valid_fields_as_string); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } - - std::vector<Dependency> vcpkg::expand_qualified_dependencies(const std::vector<std::string>& depends) - { - auto convert = [&](const std::string& depend_string) -> Dependency { - auto pos = depend_string.find(' '); - if (pos == std::string::npos) return {depend_string, ""}; - // expect of the form "\w+ \[\w+\]" - Dependency dep; - dep.name = depend_string.substr(0, pos); - if (depend_string.c_str()[pos + 1] != '[' || depend_string[depend_string.size() - 1] != ']') - { - // Error, but for now just slurp the entire string. - return {depend_string, ""}; - } - dep.qualifier = depend_string.substr(pos + 2, depend_string.size() - pos - 3); - return dep; - }; - - std::vector<vcpkg::Dependency> ret; - - for (auto&& depend_string : depends) - { - ret.push_back(convert(depend_string)); - } - - return ret; - } - - std::vector<std::string> parse_depends(const std::string& depends_string) - { - if (depends_string.empty()) - { - return {}; - } - - 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)); - break; - } - 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); - - return out; - } - - std::vector<std::string> filter_dependencies(const std::vector<vcpkg::Dependency>& deps, const Triplet& t) - { - std::vector<std::string> ret; - for (auto&& dep : deps) - { - if (dep.qualifier.empty() || t.canonical_name().find(dep.qualifier) != std::string::npos) - { - ret.push_back(dep.name); - } - } - return ret; - } - - const std::string& to_string(const Dependency& dep) { return dep.name; } -} diff --git a/toolsrc/src/StatusParagraph.cpp b/toolsrc/src/StatusParagraph.cpp deleted file mode 100644 index b9ceed278..000000000 --- a/toolsrc/src/StatusParagraph.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "pch.h" - -#include "StatusParagraph.h" -#include "vcpkglib_helpers.h" - -using namespace vcpkg::details; - -namespace vcpkg -{ - namespace BinaryParagraphRequiredField - { - static const std::string STATUS = "Status"; - } - - StatusParagraph::StatusParagraph() : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) {} - - void serialize(const StatusParagraph& pgh, std::string& out_str) - { - serialize(pgh.package, out_str); - out_str.append("Status: ") - .append(to_string(pgh.want)) - .append(" ok ") - .append(to_string(pgh.state)) - .push_back('\n'); - } - - StatusParagraph::StatusParagraph(const std::unordered_map<std::string, std::string>& fields) : package(fields) - { - std::string status_field = required_field(fields, BinaryParagraphRequiredField::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::UNKNOWN; - if (text == "install") return Want::INSTALL; - if (text == "hold") return Want::HOLD; - if (text == "deinstall") return Want::DEINSTALL; - if (text == "purge") return Want::PURGE; - return Want::ERROR_STATE; - }(std::string(mark, b)); - - if (std::distance(b, e) < 4) return; - b += 4; - - state = [](const std::string& text) { - if (text == "not-installed") return InstallState::NOT_INSTALLED; - if (text == "installed") return InstallState::INSTALLED; - if (text == "half-installed") return InstallState::HALF_INSTALLED; - return InstallState::ERROR_STATE; - }(std::string(b, e)); - } - - std::string to_string(InstallState f) - { - switch (f) - { - case InstallState::HALF_INSTALLED: return "half-installed"; - case InstallState::INSTALLED: return "installed"; - case InstallState::NOT_INSTALLED: return "not-installed"; - default: return "error"; - } - } - - std::string to_string(Want f) - { - switch (f) - { - case Want::DEINSTALL: return "deinstall"; - case Want::HOLD: return "hold"; - case Want::INSTALL: return "install"; - case Want::PURGE: return "purge"; - case Want::UNKNOWN: return "unknown"; - default: return "error"; - } - } -} diff --git a/toolsrc/src/StatusParagraphs.cpp b/toolsrc/src/StatusParagraphs.cpp deleted file mode 100644 index 27f3c30a2..000000000 --- a/toolsrc/src/StatusParagraphs.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "pch.h" - -#include "StatusParagraphs.h" -#include "vcpkg_Checks.h" -#include <algorithm> - -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& triplet) const - { - return std::find_if(begin(), end(), [&](const std::unique_ptr<StatusParagraph>& pgh) { - const PackageSpec& spec = pgh->package.spec; - return spec.name() == name && spec.triplet() == triplet; - }); - } - - StatusParagraphs::iterator StatusParagraphs::find(const std::string& name, const Triplet& triplet) - { - return std::find_if(begin(), end(), [&](const std::unique_ptr<StatusParagraph>& pgh) { - const PackageSpec& spec = pgh->package.spec; - return spec.name() == name && spec.triplet() == triplet; - }); - } - - StatusParagraphs::const_iterator StatusParagraphs::find_installed(const std::string& name, - const Triplet& triplet) const - { - const const_iterator it = find(name, triplet); - if (it != end() && (*it)->want == Want::INSTALL && (*it)->state == InstallState::INSTALLED) - { - return it; - } - - return end(); - } - - StatusParagraphs::iterator StatusParagraphs::insert(std::unique_ptr<StatusParagraph> pgh) - { - Checks::check_exit(VCPKG_LINE_INFO, pgh != nullptr, "Inserted null paragraph"); - const PackageSpec& spec = pgh->package.spec; - auto ptr = find(spec.name(), spec.triplet()); - if (ptr == end()) - { - paragraphs.push_back(std::move(pgh)); - return paragraphs.rbegin(); - } - - // consume data from provided pgh. - **ptr = std::move(*pgh); - return ptr; - } - - void serialize(const StatusParagraphs& pghs, std::string& out_str) - { - for (auto& pgh : pghs.paragraphs) - { - serialize(*pgh, out_str); - out_str.push_back('\n'); - } - } -} diff --git a/toolsrc/src/VcpkgCmdArguments.cpp b/toolsrc/src/VcpkgCmdArguments.cpp deleted file mode 100644 index 733308cb0..000000000 --- a/toolsrc/src/VcpkgCmdArguments.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include "pch.h" - -#include "VcpkgCmdArguments.h" -#include "metrics.h" -#include "vcpkg_Commands.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); - Metrics::track_property("error", "error option name"); - Commands::Help::print_usage(); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - if (option_field != nullptr) - { - System::println(System::Color::error, "Error: %s specified multiple times", option_name); - Metrics::track_property("error", "error option specified multiple times"); - Commands::Help::print_usage(); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - option_field = std::make_unique<std::string>(*arg_begin); - } - - static void parse_switch(bool new_setting, const std::string& option_name, Optional<bool>& option_field) - { - if (option_field && option_field != new_setting) - { - System::println(System::Color::error, "Error: conflicting values specified for --%s", option_name); - Metrics::track_property("error", "error conflicting switches"); - Commands::Help::print_usage(); - Checks::exit_fail(VCPKG_LINE_INFO); - } - option_field = new_setting; - } - - VcpkgCmdArguments VcpkgCmdArguments::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::to_utf8(argv[i])); - } - - return VcpkgCmdArguments::create_from_arg_sequence(v.data(), v.data() + v.size()); - } - - VcpkgCmdArguments VcpkgCmdArguments::create_from_arg_sequence(const std::string* arg_begin, - const std::string* arg_end) - { - VcpkgCmdArguments args; - - for (; arg_begin != arg_end; ++arg_begin) - { - std::string arg = *arg_begin; - - if (arg.empty()) - { - continue; - } - - if (arg[0] == '-' && arg[1] != '-') - { - Metrics::track_property("error", "error short options are not supported"); - Checks::exit_with_message(VCPKG_LINE_INFO, "Error: short options are not supported: %s", arg); - } - - if (arg[0] == '-' && arg[1] == '-') - { - // make argument case insensitive - auto& f = std::use_facet<std::ctype<char>>(std::locale()); - f.tolower(&arg[0], &arg[0] + arg.size()); - // 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.triplet); - continue; - } - if (arg == "--debug") - { - parse_switch(true, "debug", args.debug); - continue; - } - if (arg == "--sendmetrics") - { - parse_switch(true, "sendmetrics", args.sendmetrics); - continue; - } - if (arg == "--printmetrics") - { - parse_switch(true, "printmetrics", args.printmetrics); - continue; - } - if (arg == "--no-sendmetrics") - { - parse_switch(false, "sendmetrics", args.sendmetrics); - continue; - } - if (arg == "--no-printmetrics") - { - parse_switch(false, "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> VcpkgCmdArguments::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); - } - Checks::exit_fail(VCPKG_LINE_INFO); - } - - return output; - } - - void VcpkgCmdArguments::check_max_arg_count(const size_t expected_arg_count) const - { - return check_max_arg_count(expected_arg_count, ""); - } - - void VcpkgCmdArguments::check_min_arg_count(const size_t expected_arg_count) const - { - return check_min_arg_count(expected_arg_count, ""); - } - - void VcpkgCmdArguments::check_exact_arg_count(const size_t expected_arg_count) const - { - return check_exact_arg_count(expected_arg_count, ""); - } - - void VcpkgCmdArguments::check_max_arg_count(const size_t expected_arg_count, const std::string& example_text) const - { - const size_t actual_arg_count = command_arguments.size(); - if (actual_arg_count > expected_arg_count) - { - System::println(System::Color::error, - "Error: `%s` requires at most %u arguments, but %u were provided", - this->command, - expected_arg_count, - actual_arg_count); - System::print(example_text); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } - - void VcpkgCmdArguments::check_min_arg_count(const size_t expected_arg_count, const std::string& example_text) const - { - const size_t actual_arg_count = command_arguments.size(); - if (actual_arg_count < expected_arg_count) - { - System::println(System::Color::error, - "Error: `%s` requires at least %u arguments, but %u were provided", - this->command, - expected_arg_count, - actual_arg_count); - System::print(example_text); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } - - void VcpkgCmdArguments::check_exact_arg_count(const size_t expected_arg_count, - const std::string& example_text) const - { - const size_t actual_arg_count = command_arguments.size(); - if (actual_arg_count != expected_arg_count) - { - System::println(System::Color::error, - "Error: `%s` requires %u arguments, but %u were provided", - this->command, - expected_arg_count, - actual_arg_count); - System::print(example_text); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } -} diff --git a/toolsrc/src/VcpkgPaths.cpp b/toolsrc/src/VcpkgPaths.cpp deleted file mode 100644 index 3dd32de01..000000000 --- a/toolsrc/src/VcpkgPaths.cpp +++ /dev/null @@ -1,343 +0,0 @@ -#include "pch.h" - -#include "PackageSpec.h" -#include "VcpkgPaths.h" -#include "metrics.h" -#include "vcpkg_Files.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" -#include "vcpkg_expected.h" - -namespace vcpkg -{ - static bool exists_and_has_equal_or_greater_version(const std::wstring& version_cmd, - const std::array<int, 3>& expected_version) - { - static const std::regex re(R"###((\d+)\.(\d+)\.(\d+))###"); - - auto rc = System::cmd_execute_and_capture_output(Strings::wformat(LR"(%s)", version_cmd)); - if (rc.exit_code != 0) - { - return false; - } - - std::match_results<std::string::const_iterator> match; - auto found = std::regex_search(rc.output, match, re); - if (!found) - { - return false; - } - - 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 > expected_version[0] || (d1 == expected_version[0] && d2 > expected_version[1]) || - (d1 == expected_version[0] && d2 == expected_version[1] && d3 >= expected_version[2])) - { - // satisfactory version found - return true; - } - - return false; - } - - static Optional<fs::path> find_if_has_equal_or_greater_version(const std::vector<fs::path>& candidate_paths, - const std::wstring& version_check_arguments, - const std::array<int, 3>& expected_version) - { - auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { - const std::wstring cmd = Strings::wformat(LR"("%s" %s)", p.native(), version_check_arguments); - return exists_and_has_equal_or_greater_version(cmd, expected_version); - }); - - if (it != candidate_paths.cend()) - { - return std::move(*it); - } - - return nullopt; - } - - static std::vector<fs::path> find_from_PATH(const std::wstring& name) - { - const std::wstring cmd = Strings::wformat(L"where.exe %s", name); - auto out = System::cmd_execute_and_capture_output(cmd); - if (out.exit_code != 0) - { - return {}; - } - - return Util::fmap(Strings::split(out.output, "\n"), [](auto&& s) { return fs::path(s); }); - } - - static fs::path fetch_dependency(const fs::path scripts_folder, - const std::wstring& tool_name, - const fs::path& expected_downloaded_path) - { - const fs::path script = scripts_folder / "fetchDependency.ps1"; - auto install_cmd = System::create_powershell_script_cmd(script, Strings::wformat(L"-Dependency %s", tool_name)); - System::ExitCodeAndOutput rc = System::cmd_execute_and_capture_output(install_cmd); - if (rc.exit_code) - { - System::println(System::Color::error, "Launching powershell failed or was denied"); - Metrics::track_property("error", "powershell install failed"); - Metrics::track_property("installcmd", install_cmd); - Checks::exit_with_code(VCPKG_LINE_INFO, rc.exit_code); - } - - const fs::path actual_downloaded_path = Strings::trimmed(rc.output); - std::error_code ec; - auto eq = fs::stdfs::equivalent(expected_downloaded_path, actual_downloaded_path, ec); - Checks::check_exit(VCPKG_LINE_INFO, - eq && !ec, - "Expected dependency downloaded path to be %s, but was %s", - expected_downloaded_path.u8string(), - actual_downloaded_path.u8string()); - return actual_downloaded_path; - } - - static fs::path get_cmake_path(const fs::path& downloads_folder, const fs::path scripts_folder) - { - static constexpr std::array<int, 3> expected_version = {3, 8, 0}; - static const std::wstring version_check_arguments = L"--version"; - - const fs::path downloaded_copy = downloads_folder / "cmake-3.8.0-win32-x86" / "bin" / "cmake.exe"; - const std::vector<fs::path> from_path = find_from_PATH(L"cmake"); - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(downloaded_copy); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - candidate_paths.push_back(System::get_ProgramFiles_platform_bitness() / "CMake" / "bin" / "cmake.exe"); - candidate_paths.push_back(System::get_ProgramFiles_32_bit() / "CMake" / "bin"); - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, version_check_arguments, expected_version); - if (auto p = path.get()) - { - return *p; - } - - return fetch_dependency(scripts_folder, L"cmake", downloaded_copy); - } - - fs::path get_nuget_path(const fs::path& downloads_folder, const fs::path scripts_folder) - { - static constexpr std::array<int, 3> expected_version = {3, 3, 0}; - static const std::wstring version_check_arguments = L""; - - const fs::path downloaded_copy = downloads_folder / "nuget-3.5.0" / "nuget.exe"; - const std::vector<fs::path> from_path = find_from_PATH(L"nuget"); - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(downloaded_copy); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - - auto path = find_if_has_equal_or_greater_version(candidate_paths, version_check_arguments, expected_version); - if (auto p = path.get()) - { - return *p; - } - - return fetch_dependency(scripts_folder, L"nuget", downloaded_copy); - } - - fs::path get_git_path(const fs::path& downloads_folder, const fs::path scripts_folder) - { - static constexpr std::array<int, 3> expected_version = {2, 0, 0}; - static const std::wstring version_check_arguments = L"--version"; - - const fs::path downloaded_copy = downloads_folder / "MinGit-2.11.1-32-bit" / "cmd" / "git.exe"; - const std::vector<fs::path> from_path = find_from_PATH(L"git"); - - std::vector<fs::path> candidate_paths; - candidate_paths.push_back(downloaded_copy); - candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); - candidate_paths.push_back(System::get_ProgramFiles_platform_bitness() / "git" / "cmd" / "git.exe"); - candidate_paths.push_back(System::get_ProgramFiles_32_bit() / "git" / "cmd" / "git.exe"); - - const Optional<fs::path> path = - find_if_has_equal_or_greater_version(candidate_paths, version_check_arguments, expected_version); - if (auto p = path.get()) - { - return *p; - } - - return fetch_dependency(scripts_folder, L"git", downloaded_copy); - } - - Expected<VcpkgPaths> VcpkgPaths::create(const fs::path& vcpkg_root_dir) - { - std::error_code ec; - const fs::path canonical_vcpkg_root_dir = fs::stdfs::canonical(vcpkg_root_dir, ec); - if (ec) - { - return ec; - } - - VcpkgPaths paths; - paths.root = canonical_vcpkg_root_dir; - - if (paths.root.empty()) - { - Metrics::track_property("error", "Invalid vcpkg root directory"); - Checks::exit_with_message(VCPKG_LINE_INFO, "Invalid vcpkg root directory: %s", paths.root.string()); - } - - 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.scripts = paths.root / "scripts"; - - paths.buildsystems = paths.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.scripts / "ports.cmake"; - - return paths; - } - - fs::path VcpkgPaths::package_dir(const PackageSpec& spec) const { return this->packages / spec.dir(); } - - fs::path VcpkgPaths::port_dir(const PackageSpec& spec) const { return this->ports / spec.name(); } - - fs::path VcpkgPaths::build_info_file_path(const PackageSpec& spec) const - { - return this->package_dir(spec) / "BUILD_INFO"; - } - - fs::path VcpkgPaths::listfile_path(const BinaryParagraph& pgh) const - { - return this->vcpkg_dir_info / (pgh.fullstem() + ".list"); - } - - bool VcpkgPaths::is_valid_triplet(const Triplet& t) const - { - for (auto&& path : get_filesystem().get_files_non_recursive(this->triplets)) - { - std::string triplet_file_name = path.stem().generic_u8string(); - if (t.canonical_name() == triplet_file_name) // TODO: fuzzy compare - { - // t.value = triplet_file_name; // NOTE: uncomment when implementing fuzzy compare - return true; - } - } - return false; - } - - const fs::path& VcpkgPaths::get_cmake_exe() const - { - return this->cmake_exe.get_lazy([this]() { return get_cmake_path(this->downloads, this->scripts); }); - } - - const fs::path& VcpkgPaths::get_git_exe() const - { - return this->git_exe.get_lazy([this]() { return get_git_path(this->downloads, this->scripts); }); - } - - const fs::path& VcpkgPaths::get_nuget_exe() const - { - return this->nuget_exe.get_lazy([this]() { return get_nuget_path(this->downloads, this->scripts); }); - } - - static std::vector<std::string> get_VS2017_installation_instances(const VcpkgPaths& paths) - { - const fs::path script = paths.scripts / "findVisualStudioInstallationInstances.ps1"; - const std::wstring cmd = System::create_powershell_script_cmd(script); - System::ExitCodeAndOutput ec_data = System::cmd_execute_and_capture_output(cmd); - Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Could not run script to detect VS 2017 instances"); - return Strings::split(ec_data.output, "\n"); - } - - static Optional<fs::path> get_VS2015_installation_instance() - { - const Optional<std::wstring> vs2015_cmntools_optional = System::get_environment_variable(L"VS140COMNTOOLS"); - if (auto v = vs2015_cmntools_optional.get()) - { - const fs::path vs2015_cmntools = fs::path(*v).parent_path(); // The call to parent_path() is needed because - // the env variable has a trailing backslash - return vs2015_cmntools.parent_path().parent_path(); - } - - return nullopt; - } - - static Toolset find_toolset_instance(const VcpkgPaths& paths) - { - const auto& fs = paths.get_filesystem(); - - const std::vector<std::string> vs2017_installation_instances = get_VS2017_installation_instances(paths); - // Note: this will contain a mix of vcvarsall.bat locations and dumpbin.exe locations. - std::vector<fs::path> paths_examined; - - // VS2017 - for (const fs::path& instance : vs2017_installation_instances) - { - const fs::path vc_dir = instance / "VC"; - - // Skip any instances that do not have vcvarsall. - const fs::path vcvarsall_bat = vc_dir / "Auxiliary" / "Build" / "vcvarsall.bat"; - paths_examined.push_back(vcvarsall_bat); - if (!fs.exists(vcvarsall_bat)) continue; - - // Locate the "best" MSVC toolchain version - const fs::path msvc_path = vc_dir / "Tools" / "MSVC"; - std::vector<fs::path> msvc_subdirectories = fs.get_files_non_recursive(msvc_path); - Util::unstable_keep_if(msvc_subdirectories, [&fs](const fs::path& path) { return fs.is_directory(path); }); - - // Sort them so that latest comes first - std::sort(msvc_subdirectories.begin(), - msvc_subdirectories.end(), - [](const fs::path& left, const fs::path& right) { return left.filename() > right.filename(); }); - - for (const fs::path& subdir : msvc_subdirectories) - { - const fs::path dumpbin_path = subdir / "bin" / "HostX86" / "x86" / "dumpbin.exe"; - paths_examined.push_back(dumpbin_path); - if (fs.exists(dumpbin_path)) - { - return {dumpbin_path, vcvarsall_bat, L"v141"}; - } - } - } - - // VS2015 - const Optional<fs::path> vs_2015_installation_instance = get_VS2015_installation_instance(); - if (auto v = vs_2015_installation_instance.get()) - { - const fs::path vs2015_vcvarsall_bat = *v / "VC" / "vcvarsall.bat"; - - paths_examined.push_back(vs2015_vcvarsall_bat); - if (fs.exists(vs2015_vcvarsall_bat)) - { - const fs::path vs2015_dumpbin_exe = *v / "VC" / "bin" / "dumpbin.exe"; - paths_examined.push_back(vs2015_dumpbin_exe); - if (fs.exists(vs2015_dumpbin_exe)) - { - return {vs2015_dumpbin_exe, vs2015_vcvarsall_bat, L"v140"}; - } - } - } - - System::println(System::Color::error, "Could not locate a complete toolset."); - System::println("The following paths were examined:"); - for (const fs::path& path : paths_examined) - { - System::println(" %s", path.u8string()); - } - Checks::exit_fail(VCPKG_LINE_INFO); - } - - const Toolset& VcpkgPaths::get_toolset() const - { - return this->toolset.get_lazy([this]() { return find_toolset_instance(*this); }); - } - Files::Filesystem& VcpkgPaths::get_filesystem() const { return Files::get_real_filesystem(); } -} diff --git a/toolsrc/src/VersionT.cpp b/toolsrc/src/VersionT.cpp deleted file mode 100644 index 738d2ce88..000000000 --- a/toolsrc/src/VersionT.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "pch.h" - -#include "VersionT.h" -#include "vcpkg_Strings.h" - -namespace vcpkg -{ - VersionT::VersionT() : value("0.0.0") {} - VersionT::VersionT(const std::string& value) : value(value) {} - std::string VersionT::to_string() const { return value; } - bool operator==(const VersionT& left, const VersionT& right) { return left.value == right.value; } - bool operator!=(const VersionT& left, const VersionT& right) { return left.value != right.value; } - std::string to_printf_arg(const VersionT& version) { return version.value; } - - VersionDiff::VersionDiff() : left(), right() {} - VersionDiff::VersionDiff(const VersionT& left, const VersionT& right) : left(left), right(right) {} - - std::string VersionDiff::to_string() const { return Strings::format("%s -> %s", left.value, right.value); } -} diff --git a/toolsrc/src/commands_build.cpp b/toolsrc/src/commands_build.cpp deleted file mode 100644 index ec6586fa3..000000000 --- a/toolsrc/src/commands_build.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "PostBuildLint.h" -#include "StatusParagraphs.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Enums.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" - -using vcpkg::Build::BuildResult; - -namespace vcpkg::Commands::BuildCommand -{ - using Dependencies::InstallPlanAction; - using Dependencies::InstallPlanType; - - static const std::string OPTION_CHECKS_ONLY = "--checks-only"; - - void perform_and_exit(const PackageSpec& spec, - const fs::path& port_dir, - const std::unordered_set<std::string>& options, - const VcpkgPaths& paths) - { - if (options.find(OPTION_CHECKS_ONLY) != options.end()) - { - auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, spec.triplet()); - auto build_info = Build::read_build_info(paths.get_filesystem(), paths.build_info_file_path(spec)); - const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); - Checks::check_exit(VCPKG_LINE_INFO, error_count == 0); - Checks::exit_success(VCPKG_LINE_INFO); - } - - const Expected<SourceParagraph> maybe_spgh = Paragraphs::try_load_port(paths.get_filesystem(), port_dir); - Checks::check_exit(VCPKG_LINE_INFO, - !maybe_spgh.error_code(), - "Could not find package %s: %s", - spec, - maybe_spgh.error_code().message()); - const SourceParagraph& spgh = *maybe_spgh.get(); - Checks::check_exit(VCPKG_LINE_INFO, - spec.name() == spgh.name, - "The Name: field inside the CONTROL does not match the port directory: '%s' != '%s'", - spgh.name, - spec.name()); - - StatusParagraphs status_db = database_load_check(paths); - const Build::BuildPackageConfig build_config{ - spgh, spec.triplet(), paths.port_dir(spec), - }; - const auto result = Build::build_package(paths, build_config, status_db); - if (result.code == BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES) - { - System::println(System::Color::error, - "The build command requires all dependencies to be already installed."); - System::println("The following dependencies are missing:"); - System::println(""); - for (const auto& p : result.unmet_dependencies) - { - System::println(" %s", p); - } - System::println(""); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - if (result.code != BuildResult::SUCCEEDED) - { - System::println(System::Color::error, Build::create_error_message(result.code, spec)); - System::println(Build::create_user_troubleshooting_message(spec)); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - Checks::exit_success(VCPKG_LINE_INFO); - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string example = Commands::Help::create_example_string("build zlib:x64-windows"); - args.check_exact_arg_count( - 1, example); // Build only takes a single package and all dependencies must already be installed - const PackageSpec spec = - Input::check_and_get_package_spec(args.command_arguments.at(0), default_triplet, example); - Input::check_triplet(spec.triplet(), paths); - const std::unordered_set<std::string> options = - args.check_and_get_optional_command_arguments({OPTION_CHECKS_ONLY}); - perform_and_exit(spec, paths.port_dir(spec), options, paths); - } -} diff --git a/toolsrc/src/commands_build_external.cpp b/toolsrc/src/commands_build_external.cpp deleted file mode 100644 index 2712ba0cf..000000000 --- a/toolsrc/src/commands_build_external.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" - -namespace vcpkg::Commands::BuildExternal -{ - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string example = - Commands::Help::create_example_string(R"(build_external zlib2 C:\path\to\dir\with\controlfile\)"); - args.check_exact_arg_count(2, example); - const PackageSpec spec = - Input::check_and_get_package_spec(args.command_arguments.at(0), default_triplet, example); - Input::check_triplet(spec.triplet(), paths); - const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments({}); - - const fs::path port_dir = args.command_arguments.at(1); - BuildCommand::perform_and_exit(spec, port_dir, options, paths); - } -} diff --git a/toolsrc/src/commands_ci.cpp b/toolsrc/src/commands_ci.cpp deleted file mode 100644 index e9755335c..000000000 --- a/toolsrc/src/commands_ci.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "vcpkg_Build.h" -#include "vcpkg_Chrono.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Files.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" - -namespace vcpkg::Commands::CI -{ - using Dependencies::InstallPlanAction; - using Dependencies::InstallPlanType; - using Build::BuildResult; - - static std::vector<PackageSpec> load_all_package_specs(Files::Filesystem& fs, - const fs::path& ports_directory, - const Triplet& triplet) - { - std::vector<SourceParagraph> ports = Paragraphs::load_all_ports(fs, ports_directory); - std::vector<PackageSpec> specs; - for (const SourceParagraph& p : ports) - { - specs.push_back(PackageSpec::from_name_and_triplet(p.name, triplet).value_or_exit(VCPKG_LINE_INFO)); - } - - return specs; - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string example = Commands::Help::create_example_string("ci x64-windows"); - args.check_max_arg_count(1, example); - const Triplet triplet = args.command_arguments.size() == 1 - ? Triplet::from_canonical_name(args.command_arguments.at(0)) - : default_triplet; - Input::check_triplet(triplet, paths); - args.check_and_get_optional_command_arguments({}); - const std::vector<PackageSpec> specs = load_all_package_specs(paths.get_filesystem(), paths.ports, triplet); - - StatusParagraphs status_db = database_load_check(paths); - const std::vector<InstallPlanAction> install_plan = Dependencies::create_install_plan(paths, specs, status_db); - Checks::check_exit(VCPKG_LINE_INFO, !install_plan.empty(), "Install plan cannot be empty"); - - std::vector<BuildResult> results; - std::vector<std::string> timing; - const ElapsedTime timer = ElapsedTime::create_started(); - size_t counter = 0; - const size_t package_count = install_plan.size(); - for (const InstallPlanAction& action : install_plan) - { - const ElapsedTime build_timer = ElapsedTime::create_started(); - counter++; - const std::string display_name = action.spec.to_string(); - System::println("Starting package %d/%d: %s", counter, package_count, display_name); - - timing.push_back("0"); - results.push_back(BuildResult::NULLVALUE); - - try - { - switch (action.plan_type) - { - case InstallPlanType::ALREADY_INSTALLED: - results.back() = BuildResult::SUCCEEDED; - System::println(System::Color::success, "Package %s is already installed", display_name); - break; - case InstallPlanType::BUILD_AND_INSTALL: - { - System::println("Building package %s... ", display_name); - auto&& source_paragraph = action.any_paragraph.source_paragraph.value_or_exit(VCPKG_LINE_INFO); - const Build::BuildPackageConfig build_config{ - source_paragraph, action.spec.triplet(), paths.port_dir(action.spec), - }; - const auto result_ex = Build::build_package(paths, build_config, status_db); - const auto result = result_ex.code; - - timing.back() = build_timer.to_string(); - results.back() = result; - if (result != BuildResult::SUCCEEDED) - { - System::println(System::Color::error, Build::create_error_message(result, action.spec)); - continue; - } - System::println(System::Color::success, "Building package %s... done", display_name); - - const BinaryParagraph bpgh = - Paragraphs::try_load_cached_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO); - System::println("Installing package %s... ", display_name); - Install::install_package(paths, bpgh, &status_db); - System::println(System::Color::success, "Installing package %s... done", display_name); - break; - } - case InstallPlanType::INSTALL: - results.back() = BuildResult::SUCCEEDED; - System::println("Installing package %s... ", display_name); - Install::install_package( - paths, action.any_paragraph.binary_paragraph.value_or_exit(VCPKG_LINE_INFO), &status_db); - System::println(System::Color::success, "Installing package %s... done", display_name); - break; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - catch (const std::exception& e) - { - System::println(System::Color::error, "Error: Could not install package %s: %s", action.spec, e.what()); - results.back() = BuildResult::NULLVALUE; - } - System::println("Elapsed time for package %s: %s", action.spec, build_timer.to_string()); - } - - System::println("Total time taken: %s", timer.to_string()); - - for (size_t i = 0; i < results.size(); i++) - { - System::println("%s: %s: %s", install_plan[i].spec, Build::to_string(results[i]), timing[i]); - } - - std::map<BuildResult, int> summary; - for (const BuildResult& v : Build::BuildResult_values) - { - summary[v] = 0; - } - - for (const BuildResult& r : results) - { - summary[r]++; - } - - System::println("\n\nSUMMARY"); - for (const std::pair<const BuildResult, int>& entry : summary) - { - System::println(" %s: %d", Build::to_string(entry.first), entry.second); - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_contact.cpp b/toolsrc/src/commands_contact.cpp deleted file mode 100644 index 7f4161802..000000000 --- a/toolsrc/src/commands_contact.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" - -namespace vcpkg::Commands::Contact -{ - const std::string& email() - { - static const std::string s_email = R"(vcpkg@microsoft.com)"; - return s_email; - } - - void perform_and_exit(const VcpkgCmdArguments& args) - { - args.check_exact_arg_count(0); - args.check_and_get_optional_command_arguments({}); - - System::println("Send an email to %s with any feedback.", email()); - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_depends.cpp b/toolsrc/src/commands_depends.cpp deleted file mode 100644 index ad33bbad4..000000000 --- a/toolsrc/src/commands_depends.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Strings.h" -#include "vcpkg_System.h" - -namespace vcpkg::Commands::DependInfo -{ - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - static const std::string example = Commands::Help::create_example_string(R"###(depend-info)###"); - args.check_exact_arg_count(0, example); - args.check_and_get_optional_command_arguments({}); - - const std::vector<SourceParagraph> source_paragraphs = - Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); - - for (const SourceParagraph& source_paragraph : source_paragraphs) - { - auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name; }); - System::println("%s: %s", source_paragraph.name, s); - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_edit.cpp b/toolsrc/src/commands_edit.cpp deleted file mode 100644 index 4e83fcca8..000000000 --- a/toolsrc/src/commands_edit.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" - -namespace vcpkg::Commands::Edit -{ - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - auto& fs = paths.get_filesystem(); - - static const std::string example = Commands::Help::create_example_string("edit zlib"); - args.check_exact_arg_count(1, example); - args.check_and_get_optional_command_arguments({}); - const std::string port_name = args.command_arguments.at(0); - - const fs::path portpath = paths.ports / port_name; - Checks::check_exit(VCPKG_LINE_INFO, fs.is_directory(portpath), R"(Could not find port named "%s")", port_name); - - // Find the user's selected editor - std::wstring env_EDITOR; - - if (env_EDITOR.empty()) - { - const Optional<std::wstring> env_EDITOR_optional = System::get_environment_variable(L"EDITOR"); - if (auto e = env_EDITOR_optional.get()) - { - env_EDITOR = *e; - } - } - - if (env_EDITOR.empty()) - { - const fs::path CODE_EXE_PATH = System::get_ProgramFiles_32_bit() / "Microsoft VS Code/Code.exe"; - if (fs.exists(CODE_EXE_PATH)) - { - env_EDITOR = CODE_EXE_PATH; - } - } - - if (env_EDITOR.empty()) - { - static const std::array<const wchar_t*, 4> regkeys = { - LR"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1)", - LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1)", - LR"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1)", - LR"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1)", - }; - for (auto&& keypath : regkeys) - { - const Optional<std::wstring> code_installpath = - System::get_registry_string(HKEY_LOCAL_MACHINE, keypath, L"InstallLocation"); - if (auto c = code_installpath.get()) - { - auto p = fs::path(*c) / "Code.exe"; - if (fs.exists(p)) - { - env_EDITOR = p.native(); - break; - } - auto p_insiders = fs::path(*c) / "Code - Insiders.exe"; - if (fs.exists(p_insiders)) - { - env_EDITOR = p_insiders.native(); - break; - } - } - } - } - - if (env_EDITOR.empty()) - { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Visual Studio Code was not found and the environment variable EDITOR is not set"); - } - - std::wstring cmdLine = Strings::wformat( - LR"("%s" "%s" "%s" -n)", env_EDITOR, portpath.native(), (portpath / "portfile.cmake").native()); - Checks::exit_with_code(VCPKG_LINE_INFO, System::cmd_execute(cmdLine)); - } -} diff --git a/toolsrc/src/commands_env.cpp b/toolsrc/src/commands_env.cpp deleted file mode 100644 index 5e1ecc5e7..000000000 --- a/toolsrc/src/commands_env.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Build.h" -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" - -namespace vcpkg::Commands::Env -{ - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string example = Commands::Help::create_example_string(R"(env --Triplet x64-windows)"); - args.check_exact_arg_count(0, example); - args.check_and_get_optional_command_arguments({}); - - auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, default_triplet); - System::cmd_execute_clean(Build::make_build_env_cmd(pre_build_info, paths.get_toolset()) + L" && cmd"); - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_export.cpp b/toolsrc/src/commands_export.cpp deleted file mode 100644 index e46001b06..000000000 --- a/toolsrc/src/commands_export.cpp +++ /dev/null @@ -1,392 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" -#include "vcpkglib.h" -#include <regex> - -namespace vcpkg::Commands::Export -{ - using Install::InstallDir; - using Dependencies::ExportPlanAction; - using Dependencies::RequestType; - using Dependencies::ExportPlanType; - - static std::string create_nuspec_file_contents(const std::string& raw_exported_dir, - const std::string& targets_redirect_path, - const std::string& nuget_id, - const std::string& nupkg_version) - { - static constexpr auto content_template = R"( -<package> - <metadata> - <id>@NUGET_ID@</id> - <version>@VERSION@</version> - <authors>vcpkg</authors> - <description> - Vcpkg NuGet export - </description> - </metadata> - <files> - <file src="@RAW_EXPORTED_DIR@\installed\**" target="installed" /> - <file src="@RAW_EXPORTED_DIR@\scripts\**" target="scripts" /> - <file src="@RAW_EXPORTED_DIR@\.vcpkg-root" target="" /> - <file src="@TARGETS_REDIRECT_PATH@" target="build\native\@NUGET_ID@.targets" /> - </files> -</package> -)"; - - std::string nuspec_file_content = std::regex_replace(content_template, std::regex("@NUGET_ID@"), nuget_id); - nuspec_file_content = std::regex_replace(nuspec_file_content, std::regex("@VERSION@"), nupkg_version); - nuspec_file_content = - std::regex_replace(nuspec_file_content, std::regex("@RAW_EXPORTED_DIR@"), raw_exported_dir); - nuspec_file_content = - std::regex_replace(nuspec_file_content, std::regex("@TARGETS_REDIRECT_PATH@"), targets_redirect_path); - return nuspec_file_content; - } - - static std::string create_targets_redirect(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')" Project="%s" /> -</Project> -)###", - target_path, - target_path); - } - - static void print_plan(const std::map<ExportPlanType, std::vector<const ExportPlanAction*>>& group_by_plan_type) - { - static constexpr std::array<ExportPlanType, 2> order = {ExportPlanType::ALREADY_BUILT, - ExportPlanType::PORT_AVAILABLE_BUT_NOT_BUILT}; - - for (const ExportPlanType plan_type : order) - { - auto it = group_by_plan_type.find(plan_type); - if (it == group_by_plan_type.cend()) - { - continue; - } - - std::vector<const ExportPlanAction*> cont = it->second; - std::sort(cont.begin(), cont.end(), &ExportPlanAction::compare_by_name); - const std::string as_string = Strings::join("\n", cont, [](const ExportPlanAction* p) { - return Dependencies::to_output_string(p->request_type, p->spec.to_string()); - }); - - switch (plan_type) - { - case ExportPlanType::ALREADY_BUILT: - System::println("The following packages are already built and will be exported:\n%s", as_string); - continue; - case ExportPlanType::PORT_AVAILABLE_BUT_NOT_BUILT: - System::println("The following packages need to be built:\n%s", as_string); - continue; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - } - - static std::string create_export_id() - { - const tm date_time = System::get_current_date_time(); - - // Format is: YYYYmmdd-HHMMSS - // 15 characters + 1 null terminating character will be written for a total of 16 chars - char mbstr[16]; - const size_t bytes_written = std::strftime(mbstr, sizeof(mbstr), "%Y%m%d-%H%M%S", &date_time); - Checks::check_exit(VCPKG_LINE_INFO, - bytes_written == 15, - "Expected 15 bytes to be written, but %u were written", - bytes_written); - const std::string date_time_as_string(mbstr); - return ("vcpkg-export-" + date_time_as_string); - } - - static fs::path do_nuget_export(const VcpkgPaths& paths, - const std::string& nuget_id, - const fs::path& raw_exported_dir, - const fs::path& output_dir) - { - static const std::string NUPKG_VERSION = "1.0.0"; - - Files::Filesystem& fs = paths.get_filesystem(); - const fs::path& nuget_exe = paths.get_nuget_exe(); - - // This file will be placed in "build\native" in the nuget package. Therefore, go up two dirs. - const std::string targets_redirect_content = - create_targets_redirect("../../scripts/buildsystems/msbuild/vcpkg.targets"); - const fs::path targets_redirect = paths.buildsystems / "tmp" / "vcpkg.export.nuget.targets"; - fs.write_contents(targets_redirect, targets_redirect_content); - - const std::string nuspec_file_content = - create_nuspec_file_contents(raw_exported_dir.string(), targets_redirect.string(), nuget_id, NUPKG_VERSION); - const fs::path nuspec_file_path = paths.buildsystems / "tmp" / "vcpkg.export.nuspec"; - fs.write_contents(nuspec_file_path, nuspec_file_content); - - // -NoDefaultExcludes is needed for ".vcpkg-root" - const std::wstring cmd_line = - Strings::wformat(LR"("%s" pack -OutputDirectory "%s" "%s" -NoDefaultExcludes > nul)", - nuget_exe.native(), - output_dir.native(), - nuspec_file_path.native()); - - const int exit_code = System::cmd_execute_clean(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: NuGet package creation failed"); - - const fs::path output_path = output_dir / (nuget_id + ".nupkg"); - return output_path; - } - - struct ArchiveFormat final - { - enum class BackingEnum - { - ZIP = 1, - _7ZIP, - }; - - constexpr ArchiveFormat() = delete; - - constexpr ArchiveFormat(BackingEnum backing_enum, const wchar_t* extension, const wchar_t* cmake_option) - : backing_enum(backing_enum), m_extension(extension), m_cmake_option(cmake_option) - { - } - - constexpr operator BackingEnum() const { return backing_enum; } - constexpr CWStringView extension() const { return this->m_extension; } - constexpr CWStringView cmake_option() const { return this->m_cmake_option; } - - private: - BackingEnum backing_enum; - const wchar_t* m_extension; - const wchar_t* m_cmake_option; - }; - - namespace ArchiveFormatC - { - constexpr const ArchiveFormat ZIP(ArchiveFormat::BackingEnum::ZIP, L"zip", L"zip"); - constexpr const ArchiveFormat _7ZIP(ArchiveFormat::BackingEnum::_7ZIP, L"7z", L"7zip"); - } - - static fs::path do_archive_export(const VcpkgPaths& paths, - const fs::path& raw_exported_dir, - const fs::path& output_dir, - const ArchiveFormat& format) - { - const fs::path& cmake_exe = paths.get_cmake_exe(); - - const std::wstring exported_dir_filename = raw_exported_dir.filename().native(); - const std::wstring exported_archive_filename = - Strings::wformat(L"%s.%s", exported_dir_filename, format.extension()); - const fs::path exported_archive_path = (output_dir / exported_archive_filename); - - // -NoDefaultExcludes is needed for ".vcpkg-root" - const std::wstring cmd_line = Strings::wformat(LR"("%s" -E tar "cf" "%s" --format=%s -- "%s")", - cmake_exe.native(), - exported_archive_path.native(), - format.cmake_option(), - raw_exported_dir.native()); - - const int exit_code = System::cmd_execute_clean(cmd_line); - Checks::check_exit( - VCPKG_LINE_INFO, exit_code == 0, "Error: %s creation failed", exported_archive_path.generic_string()); - return exported_archive_path; - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string OPTION_DRY_RUN = "--dry-run"; - static const std::string OPTION_RAW = "--raw"; - static const std::string OPTION_NUGET = "--nuget"; - static const std::string OPTION_ZIP = "--zip"; - static const std::string OPTION_7ZIP = "--7zip"; - - // input sanitization - static const std::string example = - Commands::Help::create_example_string("export zlib zlib:x64-windows boost --nuget"); - args.check_min_arg_count(1, example); - - const std::vector<PackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { - return Input::check_and_get_package_spec(arg, default_triplet, example); - }); - for (auto&& spec : specs) - Input::check_triplet(spec.triplet(), paths); - - const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments( - {OPTION_DRY_RUN, OPTION_RAW, OPTION_NUGET, OPTION_ZIP, OPTION_7ZIP}); - const bool dryRun = options.find(OPTION_DRY_RUN) != options.cend(); - const bool raw = options.find(OPTION_RAW) != options.cend(); - const bool nuget = options.find(OPTION_NUGET) != options.cend(); - const bool zip = options.find(OPTION_ZIP) != options.cend(); - const bool _7zip = options.find(OPTION_7ZIP) != options.cend(); - - Checks::check_exit(VCPKG_LINE_INFO, - raw || nuget || zip || _7zip, - "Must provide at least one of the following options: --raw --nuget --zip --7zip"); - - // create the plan - StatusParagraphs status_db = database_load_check(paths); - std::vector<ExportPlanAction> export_plan = Dependencies::create_export_plan(paths, specs, status_db); - Checks::check_exit(VCPKG_LINE_INFO, !export_plan.empty(), "Export plan cannot be empty"); - - std::map<ExportPlanType, std::vector<const ExportPlanAction*>> group_by_plan_type; - Util::group_by(export_plan, &group_by_plan_type, [](const ExportPlanAction& p) { return p.plan_type; }); - print_plan(group_by_plan_type); - - const bool has_non_user_requested_packages = - Util::find_if(export_plan, [](const ExportPlanAction& package) -> bool { - return package.request_type != RequestType::USER_REQUESTED; - }) != export_plan.cend(); - - if (has_non_user_requested_packages) - { - System::println(System::Color::warning, - "Additional packages (*) need to be exported to complete this operation."); - } - - auto it = group_by_plan_type.find(ExportPlanType::PORT_AVAILABLE_BUT_NOT_BUILT); - if (it != group_by_plan_type.cend() && !it->second.empty()) - { - System::println(System::Color::error, "There are packages that have not been built."); - - // No need to show all of them, just the user-requested ones. Dependency resolution will handle the rest. - std::vector<const ExportPlanAction*> unbuilt = it->second; - Util::erase_remove_if( - unbuilt, [](const ExportPlanAction* a) { return a->request_type != RequestType::USER_REQUESTED; }); - - auto s = Strings::join(" ", unbuilt, [](const ExportPlanAction* a) { return a->spec.to_string(); }); - System::println("To build them, run:\n" - " vcpkg install %s", - s); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - if (dryRun) - { - Checks::exit_success(VCPKG_LINE_INFO); - } - - const std::string export_id = create_export_id(); - - Files::Filesystem& fs = paths.get_filesystem(); - const fs::path export_to_path = paths.root; - const fs::path raw_exported_dir_path = export_to_path / export_id; - std::error_code ec; - fs.remove_all(raw_exported_dir_path, ec); - fs.create_directory(raw_exported_dir_path, ec); - - // execute the plan - for (const ExportPlanAction& action : export_plan) - { - if (action.plan_type != ExportPlanType::ALREADY_BUILT) - { - Checks::unreachable(VCPKG_LINE_INFO); - } - - const std::string display_name = action.spec.to_string(); - System::println("Exporting package %s... ", display_name); - - const BinaryParagraph& binary_paragraph = - action.any_paragraph.binary_paragraph.value_or_exit(VCPKG_LINE_INFO); - const InstallDir dirs = InstallDir::from_destination_root( - raw_exported_dir_path / "installed", - action.spec.triplet().to_string(), - raw_exported_dir_path / "installed" / "vcpkg" / "info" / (binary_paragraph.fullstem() + ".list")); - - Install::install_files_and_write_listfile(paths.get_filesystem(), paths.package_dir(action.spec), dirs); - System::println(System::Color::success, "Exporting package %s... done", display_name); - } - - // Copy files needed for integration - const std::vector<fs::path> integration_files_relative_to_root = { - {".vcpkg-root"}, - {fs::path{"scripts"} / "buildsystems" / "msbuild" / "applocal.ps1"}, - {fs::path{"scripts"} / "buildsystems" / "msbuild" / "vcpkg.targets"}, - {fs::path{"scripts"} / "buildsystems" / "vcpkg.cmake"}, - {fs::path{"scripts"} / "cmake" / "vcpkg_get_windows_sdk.cmake"}, - {fs::path{"scripts"} / "getWindowsSDK.ps1"}, - {fs::path{"scripts"} / "getProgramFilesPlatformBitness.ps1"}, - {fs::path{"scripts"} / "getProgramFiles32bit.ps1"}, - }; - - for (const fs::path& file : integration_files_relative_to_root) - { - const fs::path source = paths.root / file; - const fs::path destination = raw_exported_dir_path / file; - fs.create_directories(destination.parent_path(), ec); - Checks::check_exit(VCPKG_LINE_INFO, !ec); - fs.copy_file(source, destination, fs::copy_options::overwrite_existing, ec); - Checks::check_exit(VCPKG_LINE_INFO, !ec); - } - - auto print_next_step_info = [](const fs::path& prefix) { - const fs::path cmake_toolchain = prefix / "scripts" / "buildsystems" / "vcpkg.cmake"; - const CMakeVariable cmake_variable = - CMakeVariable(L"CMAKE_TOOLCHAIN_FILE", cmake_toolchain.generic_string()); - System::println("\n" - "To use the exported libraries in CMake projects use:" - "\n" - " %s" - "\n", - Strings::to_utf8(cmake_variable.s)); - }; - - if (raw) - { - System::println( - System::Color::success, R"(Files exported at: "%s")", raw_exported_dir_path.generic_string()); - print_next_step_info(export_to_path); - } - - if (nuget) - { - System::println("Creating nuget package... "); - - const std::string nuget_id = raw_exported_dir_path.filename().string(); - const fs::path output_path = do_nuget_export(paths, nuget_id, raw_exported_dir_path, export_to_path); - System::println(System::Color::success, "Creating nuget package... done"); - System::println(System::Color::success, "NuGet package exported at: %s", output_path.generic_string()); - - System::println(R"( -With a project open, go to Tools->NuGet Package Manager->Package Manager Console and paste: - Install-Package %s -Source "%s" -)" - "\n", - nuget_id, - output_path.parent_path().u8string()); - } - - if (zip) - { - System::println("Creating zip archive... "); - const fs::path output_path = - do_archive_export(paths, raw_exported_dir_path, export_to_path, ArchiveFormatC::ZIP); - System::println(System::Color::success, "Creating zip archive... done"); - System::println(System::Color::success, "Zip archive exported at: %s", output_path.generic_string()); - print_next_step_info("[...]"); - } - - if (_7zip) - { - System::println("Creating 7zip archive... "); - const fs::path output_path = - do_archive_export(paths, raw_exported_dir_path, export_to_path, ArchiveFormatC::_7ZIP); - System::println(System::Color::success, "Creating 7zip archive... done"); - System::println(System::Color::success, "7zip archive exported at: %s", output_path.generic_string()); - print_next_step_info("[...]"); - } - - if (!raw) - { - fs.remove_all(raw_exported_dir_path, ec); - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_hash.cpp b/toolsrc/src/commands_hash.cpp deleted file mode 100644 index 0da2b031f..000000000 --- a/toolsrc/src/commands_hash.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" - -namespace vcpkg::Commands::Hash -{ - static void do_file_hash(fs::path const& path, std::wstring const& hashType) - { - auto cmd_line = Strings::wformat(LR"(CertUtil.exe -hashfile "%s" %s)", path.c_str(), hashType); - auto ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit( - VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::to_utf8(cmd_line)); - - std::string const& output = ec_data.output; - - auto start = output.find_first_of("\r\n"); - Checks::check_exit(VCPKG_LINE_INFO, - start != std::string::npos, - "Unexpected output format from command: %s", - Strings::to_utf8(cmd_line)); - - auto end = output.find_first_of("\r\n", start + 1); - Checks::check_exit(VCPKG_LINE_INFO, - end != std::string::npos, - "Unexpected output format from command: %s", - Strings::to_utf8(cmd_line)); - - auto hash = output.substr(start, end - start); - Util::erase_remove_if(hash, isspace); - System::println(hash); - } - - void perform_and_exit(const VcpkgCmdArguments& args) - { - static const std::string example = - Strings::format("The argument should be a file path\n%s", - Commands::Help::create_example_string("hash boost_1_62_0.tar.bz2")); - args.check_min_arg_count(1, example); - args.check_max_arg_count(2, example); - args.check_and_get_optional_command_arguments({}); - - if (args.command_arguments.size() == 1) - { - do_file_hash(args.command_arguments[0], L"SHA512"); - } - if (args.command_arguments.size() == 2) - { - do_file_hash(args.command_arguments[0], Strings::to_utf16(args.command_arguments[1])); - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_help.cpp b/toolsrc/src/commands_help.cpp deleted file mode 100644 index a9f9956fe..000000000 --- a/toolsrc/src/commands_help.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" - -namespace vcpkg::Commands::Help -{ - void help_topic_valid_triplet(const VcpkgPaths& paths) - { - System::println("Available architecture triplets:"); - for (auto&& path : paths.get_filesystem().get_files_non_recursive(paths.triplets)) - { - System::println(" %s", path.stem().filename().string()); - } - } - - void print_usage() - { - System::println( - "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 --outdated Uninstall all out-of-date packages\n" - " vcpkg list List installed packages\n" - " vcpkg update Display list of packages for updating\n" - " vcpkg hash <file> [alg] Hash a file by specific algorithm, default SHA512\n" - "\n" - "%s" // Integration help - "\n" - " vcpkg export <pkg>... [opt]... Exports a package\n" - " vcpkg edit <pkg> Open up a port for editing (uses %%EDITOR%%, default 'code')\n" - " vcpkg 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: %%VCPKG_DEFAULT_TRIPLET%%, 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.", - Integrate::INTEGRATE_COMMAND_HELPSTRING); - } - - std::string create_example_string(const std::string& command_and_arguments) - { - std::string cs = Strings::format("Example:\n" - " vcpkg %s", - command_and_arguments); - return cs; - } - - void print_example(const std::string& command_and_arguments) - { - System::println(create_example_string(command_and_arguments)); - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - args.check_max_arg_count(1); - args.check_and_get_optional_command_arguments({}); - - if (args.command_arguments.empty()) - { - print_usage(); - Checks::exit_success(VCPKG_LINE_INFO); - } - 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(); - Checks::exit_fail(VCPKG_LINE_INFO); - } - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_install.cpp b/toolsrc/src/commands_install.cpp deleted file mode 100644 index bebe6a3a2..000000000 --- a/toolsrc/src/commands_install.cpp +++ /dev/null @@ -1,398 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "metrics.h" -#include "vcpkg_Build.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Files.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" -#include "vcpkglib.h" - -namespace vcpkg::Commands::Install -{ - using Dependencies::InstallPlanAction; - using Dependencies::RequestType; - using Dependencies::InstallPlanType; - - InstallDir InstallDir::from_destination_root(const fs::path& destination_root, - const std::string& destination_subdirectory, - const fs::path& listfile) - { - InstallDir dirs; - dirs.m_destination = destination_root / destination_subdirectory; - dirs.m_destination_subdirectory = destination_subdirectory; - dirs.m_listfile = listfile; - return dirs; - } - - const fs::path& InstallDir::destination() const { return this->m_destination; } - - const std::string& InstallDir::destination_subdirectory() const { return this->m_destination_subdirectory; } - - const fs::path& InstallDir::listfile() const { return this->m_listfile; } - - void install_files_and_write_listfile(Files::Filesystem& fs, - const fs::path& source_dir, - const InstallDir& destination_dir) - { - std::vector<std::string> output; - std::error_code ec; - - const size_t prefix_length = source_dir.native().size(); - const fs::path& destination = destination_dir.destination(); - const std::string& destination_subdirectory = destination_dir.destination_subdirectory(); - const fs::path& listfile = destination_dir.listfile(); - - Checks::check_exit( - VCPKG_LINE_INFO, fs.exists(source_dir), "Source directory %s does not exist", source_dir.generic_string()); - fs.create_directories(destination, ec); - Checks::check_exit( - VCPKG_LINE_INFO, !ec, "Could not create destination directory %s", destination.generic_string()); - const fs::path listfile_parent = listfile.parent_path(); - fs.create_directories(listfile_parent, ec); - Checks::check_exit( - VCPKG_LINE_INFO, !ec, "Could not create directory for listfile %s", listfile.generic_string()); - - output.push_back(Strings::format(R"(%s/)", destination_subdirectory)); - auto files = fs.get_files_recursive(source_dir); - for (auto&& file : files) - { - auto status = fs.status(file, ec); - if (ec) - { - System::println(System::Color::error, "failed: %s: %s", file.u8string(), ec.message()); - continue; - } - - const std::string filename = file.filename().generic_string(); - if (fs::is_regular_file(status) && - (Strings::case_insensitive_ascii_compare(filename.c_str(), "CONTROL") == 0 || - Strings::case_insensitive_ascii_compare(filename.c_str(), "BUILD_INFO") == 0)) - { - // Do not copy the control file - continue; - } - - const std::string suffix = file.generic_u8string().substr(prefix_length + 1); - const fs::path target = destination / suffix; - - if (fs::is_directory(status)) - { - fs.create_directory(target, ec); - if (ec) - { - System::println(System::Color::error, "failed: %s: %s", target.u8string(), ec.message()); - } - - // Trailing backslash for directories - output.push_back(Strings::format(R"(%s/%s/)", destination_subdirectory, suffix)); - continue; - } - - if (fs::is_regular_file(status)) - { - if (fs.exists(target)) - { - System::println(System::Color::warning, - "File %s was already present and will be overwritten", - target.u8string(), - ec.message()); - } - fs.copy_file(file, target, fs::copy_options::overwrite_existing, ec); - if (ec) - { - System::println(System::Color::error, "failed: %s: %s", target.u8string(), ec.message()); - } - output.push_back(Strings::format(R"(%s/%s)", destination_subdirectory, suffix)); - continue; - } - - if (!fs::status_known(status)) - { - System::println(System::Color::error, "failed: %s: unknown status", file.u8string()); - continue; - } - - System::println(System::Color::error, "failed: %s: cannot handle file type", file.u8string()); - } - - std::sort(output.begin(), output.end()); - - fs.write_lines(listfile, output); - } - - static void remove_first_n_chars(std::vector<std::string>* strings, const size_t n) - { - for (std::string& s : *strings) - { - s.erase(0, n); - } - }; - - static std::vector<std::string> extract_files_in_triplet( - const std::vector<StatusParagraphAndAssociatedFiles>& pgh_and_files, const Triplet& triplet) - { - std::vector<std::string> output; - for (const StatusParagraphAndAssociatedFiles& t : pgh_and_files) - { - if (t.pgh.package.spec.triplet() != triplet) - { - continue; - } - - output.insert(output.end(), t.files.begin(), t.files.end()); - } - - std::sort(output.begin(), output.end()); - return output; - } - - static SortedVector<std::string> build_list_of_package_files(const Files::Filesystem& fs, - const fs::path& package_dir) - { - const std::vector<fs::path> package_file_paths = fs.get_files_recursive(package_dir); - const size_t package_remove_char_count = package_dir.generic_string().size() + 1; // +1 for the slash - auto package_files = Util::fmap(package_file_paths, [package_remove_char_count](const fs::path& path) { - std::string as_string = path.generic_string(); - as_string.erase(0, package_remove_char_count); - return std::move(as_string); - }); - - return SortedVector<std::string>(std::move(package_files)); - } - - static SortedVector<std::string> build_list_of_installed_files( - const std::vector<StatusParagraphAndAssociatedFiles>& pgh_and_files, const Triplet& triplet) - { - std::vector<std::string> installed_files = extract_files_in_triplet(pgh_and_files, triplet); - const size_t installed_remove_char_count = triplet.canonical_name().size() + 1; // +1 for the slash - remove_first_n_chars(&installed_files, installed_remove_char_count); - - return SortedVector<std::string>(std::move(installed_files)); - } - - static void print_plan(const std::map<InstallPlanType, std::vector<const InstallPlanAction*>>& group_by_plan_type) - { - static constexpr std::array<InstallPlanType, 3> order = { - InstallPlanType::ALREADY_INSTALLED, InstallPlanType::BUILD_AND_INSTALL, InstallPlanType::INSTALL}; - - for (const InstallPlanType plan_type : order) - { - auto it = group_by_plan_type.find(plan_type); - if (it == group_by_plan_type.cend()) - { - continue; - } - - std::vector<const InstallPlanAction*> cont = it->second; - std::sort(cont.begin(), cont.end(), &InstallPlanAction::compare_by_name); - const std::string as_string = Strings::join("\n", cont, [](const InstallPlanAction* p) { - return Dependencies::to_output_string(p->request_type, p->spec.to_string()); - }); - - switch (plan_type) - { - case InstallPlanType::ALREADY_INSTALLED: - System::println("The following packages are already installed:\n%s", as_string); - continue; - case InstallPlanType::BUILD_AND_INSTALL: - System::println("The following packages will be built and installed:\n%s", as_string); - continue; - case InstallPlanType::INSTALL: - System::println("The following packages will be installed:\n%s", as_string); - continue; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - } - - void install_package(const VcpkgPaths& paths, const BinaryParagraph& binary_paragraph, StatusParagraphs* status_db) - { - const fs::path package_dir = paths.package_dir(binary_paragraph.spec); - const Triplet& triplet = binary_paragraph.spec.triplet(); - const std::vector<StatusParagraphAndAssociatedFiles> pgh_and_files = get_installed_files(paths, *status_db); - - const SortedVector<std::string> package_files = - build_list_of_package_files(paths.get_filesystem(), package_dir); - const SortedVector<std::string> installed_files = build_list_of_installed_files(pgh_and_files, triplet); - - std::vector<std::string> intersection; - std::set_intersection(package_files.begin(), - package_files.end(), - installed_files.begin(), - installed_files.end(), - std::back_inserter(intersection)); - - if (!intersection.empty()) - { - const fs::path triplet_install_path = paths.installed / triplet.canonical_name(); - System::println(System::Color::error, - "The following files are already installed in %s and are in conflict with %s", - triplet_install_path.generic_string(), - binary_paragraph.spec); - System::print("\n "); - System::println(Strings::join("\n ", intersection)); - System::println(""); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - StatusParagraph source_paragraph; - source_paragraph.package = binary_paragraph; - source_paragraph.want = Want::INSTALL; - source_paragraph.state = InstallState::HALF_INSTALLED; - for (auto&& dep : source_paragraph.package.depends) - { - if (status_db->find_installed(dep, source_paragraph.package.spec.triplet()) == status_db->end()) - { - Checks::unreachable(VCPKG_LINE_INFO); - } - } - write_update(paths, source_paragraph); - status_db->insert(std::make_unique<StatusParagraph>(source_paragraph)); - - const InstallDir install_dir = InstallDir::from_destination_root( - paths.installed, triplet.to_string(), paths.listfile_path(binary_paragraph)); - - install_files_and_write_listfile(paths.get_filesystem(), package_dir, install_dir); - - source_paragraph.state = InstallState::INSTALLED; - write_update(paths, source_paragraph); - status_db->insert(std::make_unique<StatusParagraph>(source_paragraph)); - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string OPTION_DRY_RUN = "--dry-run"; - static const std::string OPTION_USE_HEAD_VERSION = "--head"; - static const std::string OPTION_NO_DOWNLOADS = "--no-downloads"; - - // input sanitization - static const std::string example = - Commands::Help::create_example_string("install zlib zlib:x64-windows curl boost"); - args.check_min_arg_count(1, example); - - const std::vector<PackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { - return Input::check_and_get_package_spec(arg, default_triplet, example); - }); - for (auto&& spec : specs) - Input::check_triplet(spec.triplet(), paths); - - const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments( - {OPTION_DRY_RUN, OPTION_USE_HEAD_VERSION, OPTION_NO_DOWNLOADS}); - const bool dryRun = options.find(OPTION_DRY_RUN) != options.cend(); - const bool use_head_version = options.find(OPTION_USE_HEAD_VERSION) != options.cend(); - const bool no_downloads = options.find(OPTION_NO_DOWNLOADS) != options.cend(); - - // create the plan - StatusParagraphs status_db = database_load_check(paths); - std::vector<InstallPlanAction> install_plan = Dependencies::create_install_plan(paths, specs, status_db); - Checks::check_exit(VCPKG_LINE_INFO, !install_plan.empty(), "Install plan cannot be empty"); - - // log the plan - const std::string specs_string = - Strings::join(",", install_plan, [](const InstallPlanAction& plan) { return plan.spec.to_string(); }); - Metrics::track_property("installplan", specs_string); - - std::map<InstallPlanType, std::vector<const InstallPlanAction*>> group_by_plan_type; - Util::group_by(install_plan, &group_by_plan_type, [](const InstallPlanAction& p) { return p.plan_type; }); - print_plan(group_by_plan_type); - - const bool has_non_user_requested_packages = - Util::find_if(install_plan, [](const InstallPlanAction& package) -> bool { - return package.request_type != RequestType::USER_REQUESTED; - }) != install_plan.cend(); - - if (has_non_user_requested_packages) - { - System::println("Additional packages (*) will be installed to complete this operation."); - } - - if (dryRun) - { - Checks::exit_success(VCPKG_LINE_INFO); - } - - // execute the plan - for (const InstallPlanAction& action : install_plan) - { - const std::string display_name = action.spec.to_string(); - - try - { - switch (action.plan_type) - { - case InstallPlanType::ALREADY_INSTALLED: - if (use_head_version && action.request_type == RequestType::USER_REQUESTED) - { - System::println(System::Color::warning, - "Package %s is already installed -- not building from HEAD", - display_name); - } - else - { - System::println(System::Color::success, "Package %s is already installed", display_name); - } - break; - case InstallPlanType::BUILD_AND_INSTALL: - { - Build::BuildPackageConfig build_config{ - action.any_paragraph.source_paragraph.value_or_exit(VCPKG_LINE_INFO), - action.spec.triplet(), - paths.port_dir(action.spec), - }; - - build_config.use_head_version = - use_head_version && action.request_type == RequestType::USER_REQUESTED; - build_config.no_downloads = no_downloads; - - if (build_config.use_head_version) - System::println("Building package %s from HEAD... ", display_name); - else - System::println("Building package %s... ", display_name); - - const auto result = Build::build_package(paths, build_config, status_db); - if (result.code != Build::BuildResult::SUCCEEDED) - { - System::println(System::Color::error, - Build::create_error_message(result.code, action.spec)); - System::println(Build::create_user_troubleshooting_message(action.spec)); - Checks::exit_fail(VCPKG_LINE_INFO); - } - System::println("Building package %s... done", display_name); - - const BinaryParagraph bpgh = - Paragraphs::try_load_cached_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO); - System::println("Installing package %s... ", display_name); - install_package(paths, bpgh, &status_db); - System::println(System::Color::success, "Installing package %s... done", display_name); - break; - } - case InstallPlanType::INSTALL: - if (use_head_version && action.request_type == RequestType::USER_REQUESTED) - { - System::println(System::Color::warning, - "Package %s is already built -- not building from HEAD", - display_name); - } - System::println("Installing package %s... ", display_name); - install_package( - paths, action.any_paragraph.binary_paragraph.value_or_exit(VCPKG_LINE_INFO), &status_db); - System::println(System::Color::success, "Installing package %s... done", display_name); - break; - case InstallPlanType::UNKNOWN: - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - catch (const std::exception& e) - { - System::println(System::Color::error, "Error: Could not install package %s: %s", action.spec, e.what()); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_list.cpp b/toolsrc/src/commands_list.cpp deleted file mode 100644 index 6451ac5eb..000000000 --- a/toolsrc/src/commands_list.cpp +++ /dev/null @@ -1,65 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" -#include "vcpkglib_helpers.h" - -namespace vcpkg::Commands::List -{ - static void do_print(const StatusParagraph& pgh) - { - System::println("%-27s %-16s %s", - pgh.package.displayname(), - pgh.package.version, - details::shorten_description(pgh.package.description)); - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - static const std::string example = Strings::format( - "The argument should be a substring to search for, or no argument to display all installed libraries.\n%s", - Commands::Help::create_example_string("list png")); - args.check_max_arg_count(1, example); - args.check_and_get_optional_command_arguments({}); - - const StatusParagraphs status_paragraphs = database_load_check(paths); - std::vector<StatusParagraph*> installed_packages = get_installed_ports(status_paragraphs); - - if (installed_packages.empty()) - { - System::println("No packages are installed. Did you mean `search`?"); - Checks::exit_success(VCPKG_LINE_INFO); - } - - std::sort(installed_packages.begin(), - installed_packages.end(), - [](const StatusParagraph* lhs, const StatusParagraph* rhs) -> bool { - return lhs->package.displayname() < rhs->package.displayname(); - }); - - if (args.command_arguments.size() == 0) - { - for (const StatusParagraph* status_paragraph : installed_packages) - { - do_print(*status_paragraph); - } - } - else - { - // At this point there is 1 argument - for (const StatusParagraph* status_paragraph : installed_packages) - { - const std::string displayname = status_paragraph->package.displayname(); - if (Strings::case_insensitive_ascii_find(displayname, args.command_arguments[0]) == displayname.end()) - { - continue; - } - - do_print(*status_paragraph); - } - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_remove.cpp b/toolsrc/src/commands_remove.cpp deleted file mode 100644 index e2b5d12a1..000000000 --- a/toolsrc/src/commands_remove.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Commands.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" -#include "vcpkglib.h" - -namespace vcpkg::Commands::Remove -{ - using Dependencies::RemovePlanAction; - using Dependencies::RemovePlanType; - using Dependencies::RequestType; - using Update::OutdatedPackage; - - static void remove_package(const VcpkgPaths& paths, const PackageSpec& spec, StatusParagraphs* status_db) - { - auto& fs = paths.get_filesystem(); - StatusParagraph& pkg = **status_db->find(spec.name(), spec.triplet()); - - pkg.want = Want::PURGE; - pkg.state = InstallState::HALF_INSTALLED; - write_update(paths, pkg); - - auto maybe_lines = fs.read_lines(paths.listfile_path(pkg.package)); - - if (auto lines = maybe_lines.get()) - { - std::vector<fs::path> dirs_touched; - for (auto&& suffix : *lines) - { - 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: %s", target.u8string(), ec.message()); - } - } - else if (!fs::status_known(status)) - { - System::println(System::Color::warning, "Warning: unknown status: %s", target.u8string()); - } - else - { - System::println(System::Color::warning, "Warning: %s: cannot handle file type", target.u8string()); - } - } - - auto b = dirs_touched.rbegin(); - auto e = dirs_touched.rend(); - for (; b != e; ++b) - { - if (fs.is_empty(*b)) - { - std::error_code ec; - fs.remove(*b, ec); - if (ec) - { - System::println(System::Color::error, "failed: %s", ec.message()); - } - } - } - - fs.remove(paths.listfile_path(pkg.package)); - } - - pkg.state = InstallState::NOT_INSTALLED; - write_update(paths, pkg); - } - - static void print_plan(const std::map<RemovePlanType, std::vector<const RemovePlanAction*>>& group_by_plan_type) - { - static constexpr std::array<RemovePlanType, 2> order = {RemovePlanType::NOT_INSTALLED, RemovePlanType::REMOVE}; - - for (const RemovePlanType plan_type : order) - { - auto it = group_by_plan_type.find(plan_type); - if (it == group_by_plan_type.cend()) - { - continue; - } - - std::vector<const RemovePlanAction*> cont = it->second; - std::sort(cont.begin(), cont.end(), &RemovePlanAction::compare_by_name); - const std::string as_string = Strings::join("\n", cont, [](const RemovePlanAction* p) { - return Dependencies::to_output_string(p->request_type, p->spec.to_string()); - }); - - switch (plan_type) - { - case RemovePlanType::NOT_INSTALLED: - System::println("The following packages are not installed, so not removed:\n%s", as_string); - continue; - case RemovePlanType::REMOVE: - System::println("The following packages will be removed:\n%s", as_string); - continue; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) - { - static const std::string OPTION_PURGE = "--purge"; - static const std::string OPTION_NO_PURGE = "--no-purge"; - static const std::string OPTION_RECURSE = "--recurse"; - static const std::string OPTION_DRY_RUN = "--dry-run"; - static const std::string OPTION_OUTDATED = "--outdated"; - static const std::string example = - Commands::Help::create_example_string("remove zlib zlib:x64-windows curl boost"); - const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments( - {OPTION_PURGE, OPTION_NO_PURGE, OPTION_RECURSE, OPTION_DRY_RUN, OPTION_OUTDATED}); - - StatusParagraphs status_db = database_load_check(paths); - std::vector<PackageSpec> specs; - if (options.find(OPTION_OUTDATED) != options.cend()) - { - args.check_exact_arg_count(0, example); - specs = Util::fmap(Update::find_outdated_packages(paths, status_db), - [](auto&& outdated) { return outdated.spec; }); - - if (specs.empty()) - { - System::println(System::Color::success, "There are no oudated packages."); - Checks::exit_success(VCPKG_LINE_INFO); - } - } - else - { - args.check_min_arg_count(1, example); - specs = Util::fmap(args.command_arguments, [&](auto&& arg) { - return Input::check_and_get_package_spec(arg, default_triplet, example); - }); - - for (auto&& spec : specs) - Input::check_triplet(spec.triplet(), paths); - } - - const bool alsoRemoveFolderFromPackages = options.find(OPTION_NO_PURGE) == options.end(); - if (options.find(OPTION_PURGE) != options.end() && !alsoRemoveFolderFromPackages) - { - // User specified --purge and --no-purge - System::println(System::Color::error, "Error: cannot specify both --no-purge and --purge."); - System::print(example); - Checks::exit_fail(VCPKG_LINE_INFO); - } - const bool isRecursive = options.find(OPTION_RECURSE) != options.cend(); - const bool dryRun = options.find(OPTION_DRY_RUN) != options.cend(); - - const std::vector<RemovePlanAction> remove_plan = Dependencies::create_remove_plan(specs, status_db); - Checks::check_exit(VCPKG_LINE_INFO, !remove_plan.empty(), "Remove plan cannot be empty"); - - std::map<RemovePlanType, std::vector<const RemovePlanAction*>> group_by_plan_type; - Util::group_by(remove_plan, &group_by_plan_type, [](const RemovePlanAction& p) { return p.plan_type; }); - print_plan(group_by_plan_type); - - const bool has_non_user_requested_packages = - Util::find_if(remove_plan, [](const RemovePlanAction& package) -> bool { - return package.request_type != RequestType::USER_REQUESTED; - }) != remove_plan.cend(); - - if (has_non_user_requested_packages) - { - System::println(System::Color::warning, - "Additional packages (*) need to be removed to complete this operation."); - - if (!isRecursive) - { - System::println(System::Color::warning, - "If you are sure you want to remove them, run the command with the --recurse option"); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } - - if (dryRun) - { - Checks::exit_success(VCPKG_LINE_INFO); - } - - for (const RemovePlanAction& action : remove_plan) - { - const std::string display_name = action.spec.to_string(); - - switch (action.plan_type) - { - case RemovePlanType::NOT_INSTALLED: - System::println(System::Color::success, "Package %s is not installed", display_name); - break; - case RemovePlanType::REMOVE: - System::println("Removing package %s... ", display_name); - remove_package(paths, action.spec, &status_db); - System::println(System::Color::success, "Removing package %s... done", display_name); - break; - case RemovePlanType::UNKNOWN: - default: Checks::unreachable(VCPKG_LINE_INFO); - } - - if (alsoRemoveFolderFromPackages) - { - System::println("Purging package %s... ", display_name); - Files::Filesystem& fs = paths.get_filesystem(); - std::error_code ec; - fs.remove_all(paths.packages / action.spec.dir(), ec); - System::println(System::Color::success, "Purging package %s... done", display_name); - } - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_search.cpp b/toolsrc/src/commands_search.cpp deleted file mode 100644 index 0ba7305e9..000000000 --- a/toolsrc/src/commands_search.cpp +++ /dev/null @@ -1,101 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "SourceParagraph.h" -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" -#include "vcpkglib_helpers.h" - -namespace vcpkg::Commands::Search -{ - static const std::string OPTION_GRAPH = "--graph"; // TODO: This should find a better home, eventually - - static std::string replace_dashes_with_underscore(const std::string& input) - { - std::string output = input; - std::replace(output.begin(), output.end(), '-', '_'); - return output; - } - - static std::string create_graph_as_string(const std::vector<SourceParagraph>& source_paragraphs) - { - int empty_node_count = 0; - - std::string s; - s.append("digraph G{ rankdir=LR; edge [minlen=3]; overlap=false;"); - - for (const SourceParagraph& source_paragraph : source_paragraphs) - { - if (source_paragraph.depends.empty()) - { - empty_node_count++; - continue; - } - - const std::string name = replace_dashes_with_underscore(source_paragraph.name); - s.append(Strings::format("%s;", name)); - for (const Dependency& d : source_paragraph.depends) - { - const std::string dependency_name = replace_dashes_with_underscore(d.name); - s.append(Strings::format("%s -> %s;", name, dependency_name)); - } - } - - s.append(Strings::format("empty [label=\"%d singletons...\"]; }", empty_node_count)); - return s; - } - - static void do_print(const SourceParagraph& source_paragraph) - { - System::println("%-20s %-16s %s", - source_paragraph.name, - source_paragraph.version, - details::shorten_description(source_paragraph.description)); - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - static const std::string example = Strings::format( - "The argument should be a substring to search for, or no argument to display all libraries.\n%s", - Commands::Help::create_example_string("search png")); - args.check_max_arg_count(1, example); - const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments({OPTION_GRAPH}); - - const std::vector<SourceParagraph> source_paragraphs = - Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); - if (options.find(OPTION_GRAPH) != options.cend()) - { - const std::string graph_as_string = create_graph_as_string(source_paragraphs); - System::println(graph_as_string); - Checks::exit_success(VCPKG_LINE_INFO); - } - - if (args.command_arguments.empty()) - { - for (const SourceParagraph& source_paragraph : source_paragraphs) - { - do_print(source_paragraph); - } - } - else - { - // At this point there is 1 argument - for (const SourceParagraph& source_paragraph : source_paragraphs) - { - if (Strings::case_insensitive_ascii_find(source_paragraph.name, args.command_arguments[0]) == - source_paragraph.name.end()) - { - continue; - } - - do_print(source_paragraph); - } - } - - System::println( - "\nIf your library is not listed, please open an issue at and/or consider making a pull request:\n" - " https://github.com/Microsoft/vcpkg/issues"); - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_update.cpp b/toolsrc/src/commands_update.cpp deleted file mode 100644 index e39c7dd35..000000000 --- a/toolsrc/src/commands_update.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Files.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" - -namespace vcpkg::Commands::Update -{ - bool OutdatedPackage::compare_by_name(const OutdatedPackage& left, const OutdatedPackage& right) - { - return left.spec.name() < right.spec.name(); - } - - std::vector<OutdatedPackage> find_outdated_packages(const VcpkgPaths& paths, const StatusParagraphs& status_db) - { - const std::vector<SourceParagraph> source_paragraphs = - Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); - const std::map<std::string, VersionT> src_names_to_versions = - Paragraphs::extract_port_names_and_versions(source_paragraphs); - const std::vector<StatusParagraph*> installed_packages = get_installed_ports(status_db); - - std::vector<OutdatedPackage> output; - for (const StatusParagraph* pgh : installed_packages) - { - auto it = src_names_to_versions.find(pgh->package.spec.name()); - if (it == src_names_to_versions.end()) - { - // Package was not installed from portfile - continue; - } - if (it->second != pgh->package.version) - { - output.push_back({pgh->package.spec, VersionDiff(pgh->package.version, it->second)}); - } - } - - return output; - } - - void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) - { - args.check_exact_arg_count(0); - args.check_and_get_optional_command_arguments({}); - System::println("Using local portfile versions. To update the local portfiles, use `git pull`."); - - const StatusParagraphs status_db = database_load_check(paths); - - const auto outdated_packages = - SortedVector<OutdatedPackage>(find_outdated_packages(paths, status_db), &OutdatedPackage::compare_by_name); - - if (outdated_packages.empty()) - { - System::println("No packages need updating."); - } - else - { - System::println("The following packages differ from their port versions:"); - for (auto&& package : outdated_packages) - { - System::println(" %-32s %s", package.spec, package.version_diff.to_string()); - } - System::println("\n" - "To update these packages, run\n" - " .\\vcpkg remove --outdated\n" - " .\\vcpkg install <pkgs>..."); - } - - auto version_file = paths.get_filesystem().read_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::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 " - ".\\bootstrap-vcpkg.bat to update.", - maj2, - min2, - rev2, - maj1, - min1, - rev1); - } - } - } - - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/commands_version.cpp b/toolsrc/src/commands_version.cpp deleted file mode 100644 index cd21f2561..000000000 --- a/toolsrc/src/commands_version.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "pch.h" - -#include "metrics.h" -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" - -#define STRINGIFY(...) #__VA_ARGS__ -#define MACRO_TO_STRING(X) STRINGIFY(X) - -#define VCPKG_VERSION_AS_STRING MACRO_TO_STRING(VCPKG_VERSION) - -namespace vcpkg::Commands::Version -{ - const std::string& version() - { - static const std::string s_version = -#include "../VERSION.txt" - - +std::string(VCPKG_VERSION_AS_STRING) -#ifndef NDEBUG - + std::string("-debug") -#endif - + std::string(Metrics::get_compiled_metrics_enabled() ? "" : "-external"); - return s_version; - } - - void perform_and_exit(const VcpkgCmdArguments& args) - { - args.check_exact_arg_count(0); - args.check_and_get_optional_command_arguments({}); - - System::println("Vcpkg package management program version %s\n" - "\n" - "See LICENSE.txt for license information.", - version()); - Checks::exit_success(VCPKG_LINE_INFO); - } -} diff --git a/toolsrc/src/metrics.cpp b/toolsrc/src/metrics.cpp deleted file mode 100644 index 7992f3e51..000000000 --- a/toolsrc/src/metrics.cpp +++ /dev/null @@ -1,393 +0,0 @@ -#include "pch.h" - -#include "filesystem_fs.h" -#include "metrics.h" -#include "vcpkg_Files.h" -#include "vcpkg_Strings.h" -#include "vcpkg_System.h" - -namespace vcpkg::Metrics -{ - static std::string get_current_date_time() - { - struct tm newtime; - std::array<char, 80> date; - date.fill(0); - - struct _timeb timebuffer; - - _ftime_s(&timebuffer); - time_t now = timebuffer.time; - int 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 generate_random_UUID() - { - 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 = generate_random_UUID(); - 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 = generate_random_UUID(); - std::string user_timestamp; - std::string timestamp = get_current_date_time(); - 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 get_compiled_metrics_enabled() { return DISABLE_METRICS == 0; } - - std::wstring get_SQM_user() - { - auto hkcu_sqmclient = - System::get_registry_string(HKEY_CURRENT_USER, LR"(Software\Microsoft\SQMClient)", L"UserId"); - return hkcu_sqmclient.value_or(L"{}"); - } - - void set_user_information(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 init_user_information(std::string& user_id, std::string& first_use_time) - { - user_id = generate_random_UUID(); - first_use_time = get_current_date_time(); - } - - void set_send_metrics(bool should_send_metrics) { g_should_send_metrics = should_send_metrics; } - - void set_print_metrics(bool should_print_metrics) { g_should_print_metrics = should_print_metrics; } - - void track_metric(const std::string& name, double value) { g_metricmessage.TrackMetric(name, value); } - - void track_property(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 track_property(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)) == TRUE && 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"; - - auto& fs = Files::get_real_filesystem(); - - if (true) - { - const fs::path exe_path = [&fs]() -> 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" + generate_random_UUID() + ".txt"); - fs.write_contents(vcpkg_metrics_txt_path, payload); - - const std::wstring cmdLine = - Strings::wformat(L"start %s %s", temp_folder_path_exe.native(), vcpkg_metrics_txt_path.native()); - System::cmd_execute_clean(cmdLine); - } -} diff --git a/toolsrc/src/tests.arguments.cpp b/toolsrc/src/tests.arguments.cpp new file mode 100644 index 000000000..c87281fa8 --- /dev/null +++ b/toolsrc/src/tests.arguments.cpp @@ -0,0 +1,64 @@ +#include "tests.pch.h"
+
+#pragma comment(lib, "version")
+#pragma comment(lib, "winhttp")
+
+using namespace Microsoft::VisualStudio::CppUnitTestFramework;
+
+using namespace vcpkg;
+
+namespace UnitTest1
+{
+ class ArgumentTests : public TestClass<ArgumentTests>
+ {
+ TEST_METHOD(create_from_arg_sequence_options_lower)
+ {
+ std::vector<std::string> t = {"--vcpkg-root", "C:\\vcpkg", "--debug", "--sendmetrics", "--printmetrics"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+ Assert::AreEqual("C:\\vcpkg", v.vcpkg_root_dir.get()->c_str());
+ Assert::IsTrue(v.debug && *v.debug.get());
+ Assert::IsTrue(v.sendmetrics && v.sendmetrics.get());
+ Assert::IsTrue(v.printmetrics && *v.printmetrics.get());
+ }
+
+ TEST_METHOD(create_from_arg_sequence_options_upper)
+ {
+ std::vector<std::string> t = {"--VCPKG-ROOT", "C:\\vcpkg", "--DEBUG", "--SENDMETRICS", "--PRINTMETRICS"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+ Assert::AreEqual("C:\\vcpkg", v.vcpkg_root_dir.get()->c_str());
+ Assert::IsTrue(v.debug && *v.debug.get());
+ Assert::IsTrue(v.sendmetrics && v.sendmetrics.get());
+ Assert::IsTrue(v.printmetrics && *v.printmetrics.get());
+ }
+
+ TEST_METHOD(create_from_arg_sequence_valued_options)
+ {
+ std::array<CommandSetting, 1> settings = { {{"--a", ""}} };
+ CommandStructure cmdstruct = { "", 0, SIZE_MAX, {{}, settings }, nullptr };
+
+ std::vector<std::string> t = {"--a=b", "command", "argument"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+ auto opts = v.parse_arguments(cmdstruct);
+ Assert::AreEqual("b", opts.settings["--a"].c_str());
+ Assert::AreEqual(size_t{1}, v.command_arguments.size());
+ Assert::AreEqual("argument", v.command_arguments[0].c_str());
+ Assert::AreEqual("command", v.command.c_str());
+ }
+
+ TEST_METHOD(create_from_arg_sequence_valued_options2)
+ {
+ std::array<CommandSwitch, 2> switches = { {{"--a", ""}, {"--c", ""}} };
+ std::array<CommandSetting, 2> settings = { { {"--b", ""}, {"--d", ""}} };
+ CommandStructure cmdstruct = {"", 0, SIZE_MAX, {switches, settings}, nullptr};
+
+ std::vector<std::string> t = {"--a", "--b=c"};
+ auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
+ auto opts = v.parse_arguments(cmdstruct);
+ Assert::AreEqual("c", opts.settings["--b"].c_str());
+ Assert::IsTrue(opts.settings.find("--d") == opts.settings.end());
+ Assert::IsTrue(opts.switches.find("--a") != opts.switches.end());
+ Assert::IsTrue(opts.settings.find("--c") == opts.settings.end());
+ Assert::AreEqual(size_t{0}, v.command_arguments.size());
+ }
+ };
+}
\ No newline at end of file diff --git a/toolsrc/src/tests.chrono.cpp b/toolsrc/src/tests.chrono.cpp new file mode 100644 index 000000000..269cdca58 --- /dev/null +++ b/toolsrc/src/tests.chrono.cpp @@ -0,0 +1,41 @@ +#include "tests.pch.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace Chrono = vcpkg::Chrono; + +namespace UnitTest1 +{ + class ChronoTests : public TestClass<ChronoTests> + { + TEST_METHOD(parse_time) + { + auto timestring = "1990-02-03T04:05:06.0Z"; + auto maybe_time = Chrono::CTime::parse(timestring); + + Assert::IsTrue(maybe_time.has_value()); + + Assert::AreEqual(timestring, maybe_time.get()->to_string().c_str()); + } + + TEST_METHOD(parse_time_blank) + { + auto maybe_time = Chrono::CTime::parse(""); + + Assert::IsFalse(maybe_time.has_value()); + } + + TEST_METHOD(time_difference) + { + auto maybe_time1 = Chrono::CTime::parse("1990-02-03T04:05:06.0Z"); + auto maybe_time2 = Chrono::CTime::parse("1990-02-10T04:05:06.0Z"); + + Assert::IsTrue(maybe_time1.has_value()); + Assert::IsTrue(maybe_time2.has_value()); + + auto delta = maybe_time2.get()->to_time_point() - maybe_time1.get()->to_time_point(); + + Assert::AreEqual(24 * 7, std::chrono::duration_cast<std::chrono::hours>(delta).count()); + } + }; +} diff --git a/toolsrc/src/tests.dependencies.cpp b/toolsrc/src/tests.dependencies.cpp new file mode 100644 index 000000000..f82fad4e4 --- /dev/null +++ b/toolsrc/src/tests.dependencies.cpp @@ -0,0 +1,108 @@ +#include "tests.pch.h" + +#pragma comment(lib, "version") +#pragma comment(lib, "winhttp") + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +using namespace vcpkg; +using Parse::parse_comma_list; + +namespace UnitTest1 +{ + class DependencyTests : public TestClass<DependencyTests> + { + TEST_METHOD(parse_depends_one) + { + auto v = expand_qualified_dependencies(parse_comma_list("libA (windows)")); + Assert::AreEqual(size_t(1), v.size()); + Assert::AreEqual("libA", v[0].depend.name.c_str()); + Assert::AreEqual("windows", v[0].qualifier.c_str()); + } + + TEST_METHOD(filter_depends) + { + auto deps = expand_qualified_dependencies(parse_comma_list("libA (windows), libB, libC (uwp)")); + auto v = filter_dependencies(deps, Triplet::X64_WINDOWS); + Assert::AreEqual(size_t(2), v.size()); + Assert::AreEqual("libA", v[0].c_str()); + Assert::AreEqual("libB", v[1].c_str()); + + auto v2 = filter_dependencies(deps, Triplet::ARM_UWP); + Assert::AreEqual(size_t(2), v.size()); + Assert::AreEqual("libB", v2[0].c_str()); + Assert::AreEqual("libC", v2[1].c_str()); + } + }; + + class SupportsTests : public TestClass<SupportsTests> + { + TEST_METHOD(parse_supports_all) + { + auto v = Supports::parse({ + "x64", + "x86", + "arm", + "windows", + "uwp", + "v140", + "v141", + "crt-static", + "crt-dynamic", + }); + Assert::AreNotEqual(uintptr_t(0), uintptr_t(v.get())); + + Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::UWP, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::ARM, + Supports::Platform::WINDOWS, + Supports::Linkage::STATIC, + Supports::ToolsetVersion::V141)); + } + + TEST_METHOD(parse_supports_invalid) + { + auto v = Supports::parse({"arm64"}); + Assert::AreEqual(uintptr_t(0), uintptr_t(v.get())); + Assert::AreEqual(size_t(1), v.error().size()); + Assert::AreEqual("arm64", v.error()[0].c_str()); + } + + TEST_METHOD(parse_supports_case_sensitive) + { + auto v = Supports::parse({"Windows"}); + Assert::AreEqual(uintptr_t(0), uintptr_t(v.get())); + Assert::AreEqual(size_t(1), v.error().size()); + Assert::AreEqual("Windows", v.error()[0].c_str()); + } + + TEST_METHOD(parse_supports_some) + { + auto v = Supports::parse({ + "x64", + "x86", + "windows", + }); + Assert::AreNotEqual(uintptr_t(0), uintptr_t(v.get())); + + Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::WINDOWS, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + Assert::IsFalse(v.get()->is_supported(System::CPUArchitecture::ARM, + Supports::Platform::WINDOWS, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + Assert::IsFalse(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::UWP, + Supports::Linkage::DYNAMIC, + Supports::ToolsetVersion::V140)); + Assert::IsTrue(v.get()->is_supported(System::CPUArchitecture::X64, + Supports::Platform::WINDOWS, + Supports::Linkage::STATIC, + Supports::ToolsetVersion::V141)); + } + }; +} diff --git a/toolsrc/src/tests.packagespec.cpp b/toolsrc/src/tests.packagespec.cpp new file mode 100644 index 000000000..32ad81227 --- /dev/null +++ b/toolsrc/src/tests.packagespec.cpp @@ -0,0 +1,134 @@ +#include "tests.pch.h" + +#include <tests.utils.h> + +#pragma comment(lib, "version") +#pragma comment(lib, "winhttp") + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace UnitTest1 +{ + using namespace vcpkg; + + class SpecifierConversion : public TestClass<SpecifierConversion> + { + TEST_METHOD(full_package_spec_to_feature_specs) + { + auto a_spec = PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + auto b_spec = PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + auto fspecs = FullPackageSpec::to_feature_specs({{a_spec, {"0", "1"}}, {b_spec, {"2", "3"}}}); + + Assert::AreEqual(size_t(6), fspecs.size()); + + std::array<const char*, 6> features = {"", "0", "1", "", "2", "3"}; + std::array<PackageSpec*, 6> specs = {&a_spec, &a_spec, &a_spec, &b_spec, &b_spec, &b_spec}; + + for (size_t i = 0; i < features.size(); ++i) + { + Assert::AreEqual(features[i], fspecs[i].feature().c_str()); + Assert::AreEqual(*specs[i], fspecs[i].spec()); + } + } + }; + + class SpecifierParsing : public TestClass<SpecifierParsing> + { + TEST_METHOD(parsed_specifier_from_string) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::AreEqual(size_t(0), spec->features.size()); + Assert::AreEqual("", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_from_string_with_triplet) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib:x64-uwp"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::AreEqual("x64-uwp", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_from_string_with_colons) + { + auto ec = vcpkg::ParsedSpecifier::from_string("zlib:x86-uwp:").error(); + Assert::AreEqual(vcpkg::PackageSpecParseResult::TOO_MANY_COLONS, ec); + } + + TEST_METHOD(parsed_specifier_from_string_with_feature) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[feature]:x64-uwp"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::IsTrue(spec->features.size() == 1); + Assert::AreEqual("feature", spec->features.front().c_str()); + Assert::AreEqual("x64-uwp", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_from_string_with_many_features) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[0, 1,2]"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::IsTrue(spec->features.size() == 3); + Assert::AreEqual("0", spec->features[0].c_str()); + Assert::AreEqual("1", spec->features[1].c_str()); + Assert::AreEqual("2", spec->features[2].c_str()); + Assert::AreEqual("", spec->triplet.c_str()); + } + + TEST_METHOD(parsed_specifier_wildcard_feature) + { + auto maybe_spec = vcpkg::ParsedSpecifier::from_string("zlib[*]"); + Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, maybe_spec.error()); + auto spec = maybe_spec.get(); + Assert::AreEqual("zlib", spec->name.c_str()); + Assert::IsTrue(spec->features.size() == 1); + Assert::AreEqual("*", spec->features[0].c_str()); + Assert::AreEqual("", spec->triplet.c_str()); + } + + TEST_METHOD(expand_wildcards) + { + auto zlib = + vcpkg::FullPackageSpec::from_string("zlib[0,1]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); + auto openssl = + vcpkg::FullPackageSpec::from_string("openssl[*]", Triplet::X86_UWP).value_or_exit(VCPKG_LINE_INFO); + auto specs = FullPackageSpec::to_feature_specs({zlib, openssl}); + Util::sort(specs); + auto spectargets = FeatureSpec::from_strings_and_triplet( + { + "openssl", + "zlib", + "openssl[*]", + "zlib[0]", + "zlib[1]", + }, + Triplet::X86_UWP); + Util::sort(spectargets); + Assert::IsTrue(specs.size() == spectargets.size()); + Assert::IsTrue(Util::all_equal(specs, spectargets)); + } + + TEST_METHOD(utf8_to_utf16) + { + auto str = vcpkg::Strings::to_utf16("abc"); + Assert::AreEqual(L"abc", str.c_str()); + } + + TEST_METHOD(utf8_to_utf16_with_whitespace) + { + auto str = vcpkg::Strings::to_utf16("abc -x86-windows"); + Assert::AreEqual(L"abc -x86-windows", str.c_str()); + } + }; + + TEST_CLASS(Metrics){}; +} diff --git a/toolsrc/src/tests_paragraph.cpp b/toolsrc/src/tests.paragraph.cpp index b66adc816..9a56ad9ee 100644 --- a/toolsrc/src/tests_paragraph.cpp +++ b/toolsrc/src/tests.paragraph.cpp @@ -1,22 +1,10 @@ -#include "BinaryParagraph.h" -#include "CppUnitTest.h" -#include "Paragraphs.h" -#include "vcpkg_Strings.h" +#include "tests.pch.h" #pragma comment(lib, "version") #pragma comment(lib, "winhttp") using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace Microsoft::VisualStudio::CppUnitTestFramework -{ - template<> - inline std::wstring ToString<vcpkg::PackageSpecParseResult>(const vcpkg::PackageSpecParseResult& t) - { - return ToString(static_cast<uint32_t>(t)); - } -} - namespace Strings = vcpkg::Strings; namespace UnitTest1 @@ -25,70 +13,143 @@ namespace UnitTest1 { 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()); + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + }}); + + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual("zlib", pgh->core_paragraph->name.c_str()); + Assert::AreEqual("1.2.8", pgh->core_paragraph->version.c_str()); + Assert::AreEqual("", pgh->core_paragraph->maintainer.c_str()); + Assert::AreEqual("", pgh->core_paragraph->description.c_str()); + Assert::AreEqual(size_t(0), pgh->core_paragraph->depends.size()); } TEST_METHOD(SourceParagraph_Construct_Maximum) { - 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].name.c_str()); + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "s"}, + {"Version", "v"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Build-Depends", "bd"}, + {"Default-Features", "df"}, + {"Supports", "x64"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual("s", pgh->core_paragraph->name.c_str()); + Assert::AreEqual("v", pgh->core_paragraph->version.c_str()); + Assert::AreEqual("m", pgh->core_paragraph->maintainer.c_str()); + Assert::AreEqual("d", pgh->core_paragraph->description.c_str()); + Assert::AreEqual(size_t(1), pgh->core_paragraph->depends.size()); + Assert::AreEqual("bd", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual(size_t(1), pgh->core_paragraph->default_features.size()); + Assert::AreEqual("df", pgh->core_paragraph->default_features[0].c_str()); + Assert::AreEqual(size_t(1), pgh->core_paragraph->supports.size()); + Assert::AreEqual("x64", pgh->core_paragraph->supports[0].c_str()); } TEST_METHOD(SourceParagraph_Two_Depends) { - 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].name.c_str()); - Assert::AreEqual("openssl", pgh.depends[1].name.c_str()); + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual(size_t(2), pgh->core_paragraph->depends.size()); + Assert::AreEqual("z", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name().c_str()); } TEST_METHOD(SourceParagraph_Three_Depends) { - vcpkg::SourceParagraph pgh( - {{"Source", "zlib"}, {"Version", "1.2.8"}, {"Build-Depends", "z, openssl, xyz"}}); + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "z, openssl, xyz"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual(size_t(3), pgh->core_paragraph->depends.size()); + Assert::AreEqual("z", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual("openssl", pgh->core_paragraph->depends[1].name().c_str()); + Assert::AreEqual("xyz", pgh->core_paragraph->depends[2].name().c_str()); + } - Assert::AreEqual(size_t(3), pgh.depends.size()); - Assert::AreEqual("z", pgh.depends[0].name.c_str()); - Assert::AreEqual("openssl", pgh.depends[1].name.c_str()); - Assert::AreEqual("xyz", pgh.depends[2].name.c_str()); + TEST_METHOD(SourceParagraph_Three_Supports) + { + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Supports", "x64, windows, uwp"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual(size_t(3), pgh->core_paragraph->supports.size()); + Assert::AreEqual("x64", pgh->core_paragraph->supports[0].c_str()); + Assert::AreEqual("windows", pgh->core_paragraph->supports[1].c_str()); + Assert::AreEqual("uwp", pgh->core_paragraph->supports[2].c_str()); } TEST_METHOD(SourceParagraph_Construct_Qualified_Depends) { - vcpkg::SourceParagraph pgh( - {{"Source", "zlib"}, {"Version", "1.2.8"}, {"Build-Depends", "libA [windows], libB [uwp]"}}); + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "libA (windows), libB (uwp)"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual("zlib", pgh->core_paragraph->name.c_str()); + Assert::AreEqual("1.2.8", pgh->core_paragraph->version.c_str()); + Assert::AreEqual("", pgh->core_paragraph->maintainer.c_str()); + Assert::AreEqual("", pgh->core_paragraph->description.c_str()); + Assert::AreEqual(size_t(2), pgh->core_paragraph->depends.size()); + Assert::AreEqual("libA", pgh->core_paragraph->depends[0].name().c_str()); + Assert::AreEqual("windows", pgh->core_paragraph->depends[0].qualifier.c_str()); + Assert::AreEqual("libB", pgh->core_paragraph->depends[1].name().c_str()); + Assert::AreEqual("uwp", pgh->core_paragraph->depends[1].qualifier.c_str()); + } - 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(2), pgh.depends.size()); - Assert::AreEqual("libA", pgh.depends[0].name.c_str()); - Assert::AreEqual("windows", pgh.depends[0].qualifier.c_str()); - Assert::AreEqual("libB", pgh.depends[1].name.c_str()); - Assert::AreEqual("uwp", pgh.depends[1].qualifier.c_str()); + TEST_METHOD(SourceParagraph_Default_Features) + { + auto m_pgh = + vcpkg::SourceControlFile::parse_control_file(std::vector<std::unordered_map<std::string, std::string>>{{ + {"Source", "a"}, + {"Version", "1.0"}, + {"Default-Features", "a1"}, + }}); + Assert::IsTrue(m_pgh.has_value()); + auto& pgh = *m_pgh.get(); + + Assert::AreEqual(size_t(1), pgh->core_paragraph->default_features.size()); + Assert::AreEqual("a1", pgh->core_paragraph->default_features[0].c_str()); } TEST_METHOD(BinaryParagraph_Construct_Minimum) { vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, {"Version", "1.2.8"}, {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, }); Assert::AreEqual("zlib", pgh.spec.name().c_str()); @@ -101,13 +162,15 @@ namespace UnitTest1 TEST_METHOD(BinaryParagraph_Construct_Maximum) { - vcpkg::BinaryParagraph pgh({{"Package", "s"}, - {"Version", "v"}, - {"Architecture", "x86-windows"}, - {"Multi-Arch", "same"}, - {"Maintainer", "m"}, - {"Description", "d"}, - {"Depends", "bd"}}); + vcpkg::BinaryParagraph pgh({ + {"Package", "s"}, + {"Version", "v"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Maintainer", "m"}, + {"Description", "d"}, + {"Depends", "bd"}, + }); Assert::AreEqual("s", pgh.spec.name().c_str()); Assert::AreEqual("v", pgh.version.c_str()); Assert::AreEqual("m", pgh.maintainer.c_str()); @@ -132,6 +195,35 @@ namespace UnitTest1 Assert::AreEqual("c", pgh.depends[2].c_str()); } + TEST_METHOD(BinaryParagraph_Abi) + { + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Abi", "abcd123"}, + }); + + Assert::AreEqual(size_t(0), pgh.depends.size()); + Assert::IsTrue(pgh.abi == "abcd123"); + } + + TEST_METHOD(BinaryParagraph_Default_Features) + { + vcpkg::BinaryParagraph pgh({ + {"Package", "a"}, + {"Version", "1.0"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Default-Features", "a1"}, + }); + + Assert::AreEqual(size_t(0), pgh.depends.size()); + Assert::AreEqual(size_t(1), pgh.default_features.size()); + Assert::IsTrue(pgh.default_features[0] == "a1"); + } + TEST_METHOD(parse_paragraphs_empty) { const char* str = ""; @@ -275,7 +367,10 @@ namespace UnitTest1 TEST_METHOD(BinaryParagraph_serialize_min) { vcpkg::BinaryParagraph pgh({ - {"Package", "zlib"}, {"Version", "1.2.8"}, {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, }); std::string ss = Strings::serialize(pgh); auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); @@ -325,44 +420,20 @@ namespace UnitTest1 Assert::AreEqual("a, b, c", pghs[0]["Depends"].c_str()); } - TEST_METHOD(package_spec_parse) - { - vcpkg::Expected<vcpkg::PackageSpec> spec = - vcpkg::PackageSpec::from_string("zlib", vcpkg::Triplet::X86_WINDOWS); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, - vcpkg::to_package_spec_parse_result(spec.error_code())); - Assert::AreEqual("zlib", spec.get()->name().c_str()); - Assert::AreEqual(vcpkg::Triplet::X86_WINDOWS.canonical_name(), spec.get()->triplet().canonical_name()); - } - - TEST_METHOD(package_spec_parse_with_arch) - { - vcpkg::Expected<vcpkg::PackageSpec> spec = - vcpkg::PackageSpec::from_string("zlib:x64-uwp", vcpkg::Triplet::X86_WINDOWS); - Assert::AreEqual(vcpkg::PackageSpecParseResult::SUCCESS, - vcpkg::to_package_spec_parse_result(spec.error_code())); - Assert::AreEqual("zlib", spec.get()->name().c_str()); - Assert::AreEqual(vcpkg::Triplet::X64_UWP.canonical_name(), spec.get()->triplet().canonical_name()); - } - - TEST_METHOD(package_spec_parse_with_multiple_colon) - { - auto ec = vcpkg::PackageSpec::from_string("zlib:x86-uwp:", vcpkg::Triplet::X86_WINDOWS).error_code(); - Assert::AreEqual(vcpkg::PackageSpecParseResult::TOO_MANY_COLONS, vcpkg::to_package_spec_parse_result(ec)); - } - - TEST_METHOD(utf8_to_utf16) + TEST_METHOD(BinaryParagraph_serialize_abi) { - auto str = vcpkg::Strings::to_utf16("abc"); - Assert::AreEqual(L"abc", str.c_str()); - } - - TEST_METHOD(utf8_to_utf16_with_whitespace) - { - auto str = vcpkg::Strings::to_utf16("abc -x86-windows"); - Assert::AreEqual(L"abc -x86-windows", str.c_str()); + vcpkg::BinaryParagraph pgh({ + {"Package", "zlib"}, + {"Version", "1.2.8"}, + {"Architecture", "x86-windows"}, + {"Multi-Arch", "same"}, + {"Depends", "a, b, c"}, + {"Abi", "123abc"}, + }); + std::string ss = Strings::serialize(pgh); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss).value_or_exit(VCPKG_LINE_INFO); + Assert::AreEqual(size_t(1), pghs.size()); + Assert::AreEqual("123abc", pghs[0]["Abi"].c_str()); } }; - - TEST_CLASS(Metrics){}; } diff --git a/toolsrc/src/tests.pch.cpp b/toolsrc/src/tests.pch.cpp new file mode 100644 index 000000000..bdddab76a --- /dev/null +++ b/toolsrc/src/tests.pch.cpp @@ -0,0 +1 @@ +#include "tests.pch.h"
diff --git a/toolsrc/src/tests.plan.cpp b/toolsrc/src/tests.plan.cpp new file mode 100644 index 000000000..238aa7032 --- /dev/null +++ b/toolsrc/src/tests.plan.cpp @@ -0,0 +1,1258 @@ +#include "tests.pch.h" + +#include <tests.utils.h> + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +using namespace vcpkg; + +namespace UnitTest1 +{ + static std::unique_ptr<SourceControlFile> make_control_file( + const char* name, + const char* depends, + const std::vector<std::pair<const char*, const char*>>& features = {}, + const std::vector<const char*>& default_features = {}) + { + using Pgh = std::unordered_map<std::string, std::string>; + std::vector<Pgh> scf_pghs; + scf_pghs.push_back(Pgh{{"Source", name}, + {"Version", "0"}, + {"Build-Depends", depends}, + {"Default-Features", Strings::join(", ", default_features)}}); + for (auto&& feature : features) + { + scf_pghs.push_back(Pgh{ + {"Feature", feature.first}, + {"Description", "feature"}, + {"Build-Depends", feature.second}, + }); + } + auto m_pgh = vcpkg::SourceControlFile::parse_control_file(std::move(scf_pghs)); + Assert::IsTrue(m_pgh.has_value()); + return std::move(*m_pgh.get()); + } + + /// <summary> + /// Assert that the given action an install of given features from given package. + /// </summary> + static void features_check(Dependencies::AnyAction* install_action, + std::string pkg_name, + std::vector<std::string> vec, + const Triplet& triplet = Triplet::X86_WINDOWS) + { + Assert::IsTrue(install_action->install_action.has_value()); + const auto& plan = install_action->install_action.value_or_exit(VCPKG_LINE_INFO); + const auto& feature_list = plan.feature_list; + + Assert::AreEqual(plan.spec.triplet().to_string().c_str(), triplet.to_string().c_str()); + + Assert::AreEqual(pkg_name.c_str(), plan.source_control_file.get()->core_paragraph->name.c_str()); + Assert::AreEqual(size_t(vec.size()), feature_list.size()); + + for (auto&& feature_name : vec) + { + if (feature_name == "core" || feature_name == "") + { + Assert::IsTrue(Util::find(feature_list, "core") != feature_list.end() || + Util::find(feature_list, "") != feature_list.end()); + continue; + } + Assert::IsTrue(Util::find(feature_list, feature_name) != feature_list.end()); + } + } + + /// <summary> + /// Assert that the given action is a remove of given package. + /// </summary> + static void remove_plan_check(Dependencies::AnyAction* remove_action, + std::string pkg_name, + const Triplet& triplet = Triplet::X86_WINDOWS) + { + const auto& plan = remove_action->remove_action.value_or_exit(VCPKG_LINE_INFO); + Assert::AreEqual(plan.spec.triplet().to_string().c_str(), triplet.to_string().c_str()); + Assert::AreEqual(pkg_name.c_str(), plan.spec.name().c_str()); + } + + /// <summary> + /// Map of source control files by their package name. + /// </summary> + struct PackageSpecMap + { + std::unordered_map<std::string, SourceControlFile> map; + Triplet triplet; + PackageSpecMap(const Triplet& t = Triplet::X86_WINDOWS) noexcept { triplet = t; } + + PackageSpec emplace(const char* name, + const char* depends = "", + const std::vector<std::pair<const char*, const char*>>& features = {}, + const std::vector<const char*>& default_features = {}) + { + return emplace(std::move(*make_control_file(name, depends, features, default_features))); + } + PackageSpec emplace(vcpkg::SourceControlFile&& scf) + { + auto spec = PackageSpec::from_name_and_triplet(scf.core_paragraph->name, triplet); + Assert::IsTrue(spec.has_value()); + map.emplace(scf.core_paragraph->name, std::move(scf)); + return PackageSpec{*spec.get()}; + } + }; + + class InstallPlanTests : public TestClass<InstallPlanTests> + { + TEST_METHOD(basic_install_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "b"); + auto spec_b = spec_map.emplace("b", "c"); + auto spec_c = spec_map.emplace("c"); + + Dependencies::MapPortFileProvider map_port(spec_map.map); + auto install_plan = Dependencies::create_feature_install_plan( + map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(3), install_plan.size()); + Assert::AreEqual("c", install_plan[0].spec().name().c_str()); + Assert::AreEqual("b", install_plan[1].spec().name().c_str()); + Assert::AreEqual("a", install_plan[2].spec().name().c_str()); + } + + TEST_METHOD(multiple_install_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "d"); + auto spec_b = spec_map.emplace("b", "d, e"); + auto spec_c = spec_map.emplace("c", "e, h"); + auto spec_d = spec_map.emplace("d", "f, g, h"); + auto spec_e = spec_map.emplace("e", "g"); + auto spec_f = spec_map.emplace("f"); + auto spec_g = spec_map.emplace("g"); + auto spec_h = spec_map.emplace("h"); + + Dependencies::MapPortFileProvider map_port(spec_map.map); + auto install_plan = Dependencies::create_feature_install_plan( + map_port, + {FeatureSpec{spec_a, ""}, FeatureSpec{spec_b, ""}, FeatureSpec{spec_c, ""}}, + StatusParagraphs(std::move(status_paragraphs))); + + auto iterator_pos = [&](const PackageSpec& spec) -> int { + auto it = std::find_if( + install_plan.begin(), install_plan.end(), [&](auto& action) { return action.spec() == spec; }); + Assert::IsTrue(it != install_plan.end()); + return (int)(it - install_plan.begin()); + }; + + int a_pos = iterator_pos(spec_a), b_pos = iterator_pos(spec_b), c_pos = iterator_pos(spec_c), + d_pos = iterator_pos(spec_d), e_pos = iterator_pos(spec_e), f_pos = iterator_pos(spec_f), + g_pos = iterator_pos(spec_g), h_pos = iterator_pos(spec_h); + + Assert::IsTrue(a_pos > d_pos); + Assert::IsTrue(b_pos > e_pos); + Assert::IsTrue(b_pos > d_pos); + Assert::IsTrue(c_pos > e_pos); + Assert::IsTrue(c_pos > h_pos); + Assert::IsTrue(d_pos > f_pos); + Assert::IsTrue(d_pos > g_pos); + Assert::IsTrue(d_pos > h_pos); + Assert::IsTrue(e_pos > g_pos); + } + + TEST_METHOD(existing_package_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a")}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(1), install_plan.size()); + auto p = install_plan[0].install_action.get(); + Assert::IsNotNull(p); + Assert::AreEqual("a", p->spec.name().c_str()); + Assert::AreEqual(Dependencies::InstallPlanType::ALREADY_INSTALLED, p->plan_type); + Assert::AreEqual(Dependencies::RequestType::USER_REQUESTED, p->request_type); + } + + TEST_METHOD(user_requested_package_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b")}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(2), install_plan.size()); + auto p = install_plan[0].install_action.get(); + Assert::IsNotNull(p); + Assert::AreEqual("b", p->spec.name().c_str()); + Assert::AreEqual(Dependencies::InstallPlanType::BUILD_AND_INSTALL, p->plan_type); + Assert::AreEqual(Dependencies::RequestType::AUTO_SELECTED, p->request_type); + + auto p2 = install_plan[1].install_action.get(); + Assert::IsNotNull(p2); + Assert::AreEqual("a", p2->spec.name().c_str()); + Assert::AreEqual(Dependencies::InstallPlanType::BUILD_AND_INSTALL, p2->plan_type); + Assert::AreEqual(Dependencies::RequestType::USER_REQUESTED, p2->request_type); + } + + TEST_METHOD(long_install_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("j", "k")); + status_paragraphs.push_back(make_status_pgh("k")); + + PackageSpecMap spec_map; + + auto spec_a = spec_map.emplace("a", "b, c, d, e, f, g, h, j, k"); + auto spec_b = spec_map.emplace("b", "c, d, e, f, g, h, j, k"); + auto spec_c = spec_map.emplace("c", "d, e, f, g, h, j, k"); + auto spec_d = spec_map.emplace("d", "e, f, g, h, j, k"); + auto spec_e = spec_map.emplace("e", "f, g, h, j, k"); + auto spec_f = spec_map.emplace("f", "g, h, j, k"); + auto spec_g = spec_map.emplace("g", "h, j, k"); + auto spec_h = spec_map.emplace("h", "j, k"); + auto spec_j = spec_map.emplace("j", "k"); + auto spec_k = spec_map.emplace("k"); + + Dependencies::MapPortFileProvider map_port(spec_map.map); + auto install_plan = Dependencies::create_feature_install_plan( + map_port, {FeatureSpec{spec_a, ""}}, StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(8), install_plan.size()); + Assert::AreEqual("h", install_plan[0].spec().name().c_str()); + Assert::AreEqual("g", install_plan[1].spec().name().c_str()); + Assert::AreEqual("f", install_plan[2].spec().name().c_str()); + Assert::AreEqual("e", install_plan[3].spec().name().c_str()); + Assert::AreEqual("d", install_plan[4].spec().name().c_str()); + Assert::AreEqual("c", install_plan[5].spec().name().c_str()); + Assert::AreEqual("b", install_plan[6].spec().name().c_str()); + Assert::AreEqual("a", install_plan[7].spec().name().c_str()); + } + + TEST_METHOD(basic_feature_test_1) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a", "b, b[b1]")); + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.push_back(make_status_feature_pgh("b", "b1")); + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b, b[b1]", {{"a1", "b[b2]"}}), {"a1"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(4), install_plan.size()); + remove_plan_check(&install_plan[0], "a"); + remove_plan_check(&install_plan[1], "b"); + features_check(&install_plan[2], "b", {"b1", "core", "b1"}); + features_check(&install_plan[3], "a", {"a1", "core"}); + } + + TEST_METHOD(basic_feature_test_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[b1]", {{"a1", "b[b2]"}}), {"a1"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}, {"b3", ""}})}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(2), install_plan.size()); + features_check(&install_plan[0], "b", {"b1", "b2", "core"}); + features_check(&install_plan[1], "a", {"a1", "core"}); + } + + TEST_METHOD(basic_feature_test_3) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_c, spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(4), install_plan.size()); + remove_plan_check(&install_plan[0], "a"); + features_check(&install_plan[1], "b", {"core"}); + features_check(&install_plan[2], "a", {"a1", "core"}); + features_check(&install_plan[3], "c", {"core"}); + } + + TEST_METHOD(basic_feature_test_4) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.push_back(make_status_feature_pgh("a", "a1", "")); + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}})}; + auto spec_b = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_c}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(1), install_plan.size()); + features_check(&install_plan[0], "c", {"core"}); + } + + TEST_METHOD(basic_feature_test_5) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map; + + auto spec_a = + FullPackageSpec{spec_map.emplace("a", "", {{"a1", "b[b1]"}, {"a2", "b[b2]"}, {"a3", "a[a2]"}}), {"a3"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}, {"b2", ""}})}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(2), install_plan.size()); + features_check(&install_plan[0], "b", {"core", "b2"}); + features_check(&install_plan[1], "a", {"core", "a3", "a2"}); + } + + TEST_METHOD(basic_feature_test_6) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("b")); + + PackageSpecMap spec_map; + auto spec_a = FullPackageSpec{spec_map.emplace("a", "b[core]"), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_a, spec_b}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(3), install_plan.size()); + remove_plan_check(&install_plan[0], "b"); + features_check(&install_plan[1], "b", {"core", "b1"}); + features_check(&install_plan[2], "a", {"core"}); + } + + TEST_METHOD(basic_feature_test_7) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("x", "b")); + status_paragraphs.push_back(make_status_pgh("b")); + + PackageSpecMap spec_map; + + auto spec_a = FullPackageSpec{spec_map.emplace("a")}; + auto spec_x = FullPackageSpec{spec_map.emplace("x", "a"), {"core"}}; + auto spec_b = FullPackageSpec{spec_map.emplace("b", "", {{"b1", ""}}), {"b1"}}; + + auto install_plan = + Dependencies::create_feature_install_plan(spec_map.map, + FullPackageSpec::to_feature_specs({spec_b}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::AreEqual(size_t(5), install_plan.size()); + remove_plan_check(&install_plan[0], "x"); + remove_plan_check(&install_plan[1], "b"); + + // TODO: order here may change but A < X, and B anywhere + features_check(&install_plan[2], "b", {"core", "b1"}); + features_check(&install_plan[3], "a", {"core"}); + features_check(&install_plan[4], "x", {"core"}); + } + + TEST_METHOD(basic_feature_test_8) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + spec_map.triplet = Triplet::X86_WINDOWS; + auto spec_a_86 = FullPackageSpec{spec_map.emplace("a", "b", {{"a1", ""}}), {"core"}}; + auto spec_b_86 = FullPackageSpec{spec_map.emplace("b")}; + auto spec_c_86 = FullPackageSpec{spec_map.emplace("c", "a[a1]"), {"core"}}; + + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({spec_c_64, spec_a_86, spec_a_64, spec_c_86}), + StatusParagraphs(std::move(status_paragraphs))); + + remove_plan_check(&install_plan[0], "a", Triplet::X64_WINDOWS); + remove_plan_check(&install_plan[1], "a"); + features_check(&install_plan[2], "b", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[3], "a", {"a1", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[4], "c", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[5], "b", {"core"}); + features_check(&install_plan[6], "a", {"a1", "core"}); + features_check(&install_plan[7], "c", {"core"}); + } + + TEST_METHOD(install_all_features_test) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + Assert::IsTrue(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"0", "1", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_test_1) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" with default features "1" and features "0" and "1". + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect the default feature "1" to be installed, but not "0" + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"1", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_test_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("a", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // Add a port "a" of which "core" is already installed, but we will + // install the default features "explicitly" + // "a" has two features, of which "a1" is default. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get removed for rebuild and then installed with default + // features. + Assert::IsTrue(install_plan.size() == 2); + remove_plan_check(&install_plan[0], "a", Triplet::X64_WINDOWS); + features_check(&install_plan[1], "a", {"a1", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_test_3) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // "a" has two features, of which "a1" is default. + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"a0", ""}, {"a1", ""}}, {"a1"}); + + // Explicitly install "a" without default features + auto install_specs = FullPackageSpec::from_string("a[core]", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect the default feature not to get installed. + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_of_dependency_test_1) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed and defaults of "b" through the dependency, + // as no explicit features of "b" are installed by the user. + Assert::IsTrue(install_plan.size() == 2); + features_check(&install_plan[0], "b", {"b1", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[1], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(do_not_install_default_features_of_existing_dependency) + { + // Add a port "a" which depends on the core of "b" + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + // "b[core]" is already installed + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed, but not require rebuilding "b" + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_default_features_of_dependency_test_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("b")); + status_paragraphs.back()->package.spec = + PackageSpec::from_name_and_triplet("b", Triplet::X64_WINDOWS).value_or_exit(VCPKG_LINE_INFO); + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "b[core]"); + // "b" has two features, of which "b1" is default. + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + // Expect "a" to get installed, not the defaults of "b", as the required + // dependencies are already there, installed explicitly by the user. + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(install_plan_action_dependencies) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_c = spec_map.emplace("c"); + auto spec_b = spec_map.emplace("b", "c"); + spec_map.emplace("a", "b"); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"core"}, Triplet::X64_WINDOWS); + + features_check(&install_plan[1], "b", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[1].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_c}); + + features_check(&install_plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[2].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_b}); + } + + TEST_METHOD(install_plan_action_dependencies_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_c = spec_map.emplace("c"); + auto spec_b = spec_map.emplace("b", "c"); + spec_map.emplace("a", "c, b"); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"core"}, Triplet::X64_WINDOWS); + + features_check(&install_plan[1], "b", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[1].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_c}); + + features_check(&install_plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[2].install_action.get()->computed_dependencies == + std::vector<PackageSpec>{spec_b, spec_c}); + } + + TEST_METHOD(install_plan_action_dependencies_3) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + // Add a port "a" which depends on the core of "b", which was already + // installed explicitly + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + spec_map.emplace("a", "", {{"0", ""}, {"1", "a[0]"}}, {"1"}); + + // Install "a" (without explicit feature specification) + auto install_specs = FullPackageSpec::from_string("a", Triplet::X64_WINDOWS); + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 1); + features_check(&install_plan[0], "a", {"1", "0", "core"}, Triplet::X64_WINDOWS); + Assert::IsTrue(install_plan[0].install_action.get()->computed_dependencies == std::vector<PackageSpec>{}); + } + + TEST_METHOD(install_with_default_features) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto b_spec = spec_map.emplace("b", "", {{"0", ""}}, {"0"}); + auto a_spec = spec_map.emplace("a", "b[core]", {{"0", ""}}); + + // Install "a" and indicate that "b" should not install default features + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, {FeatureSpec{a_spec, "0"}, FeatureSpec{b_spec, "core"}}, status_db); + + Assert::IsTrue(install_plan.size() == 3); + remove_plan_check(&install_plan[0], "a"); + features_check(&install_plan[1], "b", {"core"}); + features_check(&install_plan[2], "a", {"0", "core"}); + } + + TEST_METHOD(upgrade_with_default_features_1) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "", "1")); + pghs.push_back(make_status_feature_pgh("a", "0")); + StatusParagraphs status_db(std::move(pghs)); + + // Add a port "a" of which "core" and "0" are already installed. + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}}, {"1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should not install the default feature + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + remove_plan_check(&plan[0], "a"); + features_check(&plan[1], "a", {"core", "0"}); + } + + TEST_METHOD(upgrade_with_default_features_2) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + // B is currently installed _without_ default feature b0 + pghs.push_back(make_status_pgh("b", "", "b0", "x64-windows")); + pghs.push_back(make_status_pgh("a", "b[core]", "", "x64-windows")); + + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a = spec_map.emplace("a", "b[core]"); + auto spec_b = spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0", "b1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + graph.upgrade(spec_b); + auto plan = graph.serialize(); + + // The upgrade should install the new default feature b1 but not b0 + Assert::AreEqual(size_t(4), plan.size()); + remove_plan_check(&plan[0], "a", Triplet::X64_WINDOWS); + remove_plan_check(&plan[1], "b", Triplet::X64_WINDOWS); + features_check(&plan[2], "b", {"core", "b1"}, Triplet::X64_WINDOWS); + features_check(&plan[3], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(upgrade_with_default_features_3) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + // note: unrelated package due to x86 triplet + pghs.push_back(make_status_pgh("b", "", "", "x86-windows")); + pghs.push_back(make_status_pgh("a", "", "", "x64-windows")); + + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a = spec_map.emplace("a", "b[core]"); + spec_map.emplace("b", "", {{"b0", ""}, {"b1", ""}}, {"b0"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should install the default feature + Assert::AreEqual(size_t(3), plan.size()); + remove_plan_check(&plan[0], "a", Triplet::X64_WINDOWS); + features_check(&plan[1], "b", {"b0", "core"}, Triplet::X64_WINDOWS); + features_check(&plan[2], "a", {"core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(upgrade_with_new_default_feature) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a", "", "0", "x86-windows")); + + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"0", ""}, {"1", ""}, {"2", ""}}, {"0", "1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + auto plan = graph.serialize(); + + // The upgrade should install the new default feature but not the old default feature 0 + Assert::AreEqual(size_t(2), plan.size()); + remove_plan_check(&plan[0], "a", Triplet::X86_WINDOWS); + features_check(&plan[1], "a", {"core", "1"}, Triplet::X86_WINDOWS); + } + + TEST_METHOD(transitive_features_test) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", "b[0]"}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", "c[0]"}}), {"core"}}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + Assert::IsTrue(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[1], "b", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[2], "a", {"0", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(no_transitive_features_test) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "b", {{"0", ""}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "c", {{"0", ""}}), {"core"}}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + Assert::IsTrue(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[1], "b", {"core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[2], "a", {"0", "core"}, Triplet::X64_WINDOWS); + } + + TEST_METHOD(only_transitive_features_test) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + + PackageSpecMap spec_map(Triplet::X64_WINDOWS); + auto spec_a_64 = FullPackageSpec{spec_map.emplace("a", "", {{"0", "b[0]"}}), {"core"}}; + auto spec_b_64 = FullPackageSpec{spec_map.emplace("b", "", {{"0", "c[0]"}}), {"core"}}; + auto spec_c_64 = FullPackageSpec{spec_map.emplace("c", "", {{"0", ""}}), {"core"}}; + + auto install_specs = FullPackageSpec::from_string("a[*]", Triplet::X64_WINDOWS); + Assert::IsTrue(install_specs.has_value()); + if (!install_specs.has_value()) return; + auto install_plan = Dependencies::create_feature_install_plan( + spec_map.map, + FullPackageSpec::to_feature_specs({install_specs.value_or_exit(VCPKG_LINE_INFO)}), + StatusParagraphs(std::move(status_paragraphs))); + + Assert::IsTrue(install_plan.size() == 3); + features_check(&install_plan[0], "c", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[1], "b", {"0", "core"}, Triplet::X64_WINDOWS); + features_check(&install_plan[2], "a", {"0", "core"}, Triplet::X64_WINDOWS); + } + }; + + class RemovePlanTests : public TestClass<RemovePlanTests> + { + TEST_METHOD(basic_remove_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); + + Assert::AreEqual(size_t(1), remove_plan.size()); + Assert::AreEqual("a", remove_plan[0].spec.name().c_str()); + } + + TEST_METHOD(recurse_remove_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b", "a")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); + + Assert::AreEqual(size_t(2), remove_plan.size()); + Assert::AreEqual("b", remove_plan[0].spec.name().c_str()); + Assert::AreEqual("a", remove_plan[1].spec.name().c_str()); + } + + TEST_METHOD(features_depend_remove_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b")); + pghs.push_back(make_status_feature_pgh("b", "0", "a")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("a")}, status_db); + + Assert::AreEqual(size_t(2), remove_plan.size()); + Assert::AreEqual("b", remove_plan[0].spec.name().c_str()); + Assert::AreEqual("a", remove_plan[1].spec.name().c_str()); + } + + TEST_METHOD(features_depend_remove_scheme_once_removed) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("expat")); + pghs.push_back(make_status_pgh("vtk", "expat")); + pghs.push_back(make_status_pgh("opencv")); + pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan({unsafe_pspec("expat")}, status_db); + + Assert::AreEqual(size_t(3), remove_plan.size()); + Assert::AreEqual("opencv", remove_plan[0].spec.name().c_str()); + Assert::AreEqual("vtk", remove_plan[1].spec.name().c_str()); + Assert::AreEqual("expat", remove_plan[2].spec.name().c_str()); + } + + TEST_METHOD(features_depend_remove_scheme_once_removed_x64) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("expat", "", "", "x64")); + pghs.push_back(make_status_pgh("vtk", "expat", "", "x64")); + pghs.push_back(make_status_pgh("opencv", "", "", "x64")); + pghs.push_back(make_status_feature_pgh("opencv", "vtk", "vtk", "x64")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan( + {unsafe_pspec("expat", Triplet::from_canonical_name("x64"))}, status_db); + + Assert::AreEqual(size_t(3), remove_plan.size()); + Assert::AreEqual("opencv", remove_plan[0].spec.name().c_str()); + Assert::AreEqual("vtk", remove_plan[1].spec.name().c_str()); + Assert::AreEqual("expat", remove_plan[2].spec.name().c_str()); + } + + TEST_METHOD(features_depend_core_remove_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("curl", "", "", "x64")); + pghs.push_back(make_status_pgh("cpr", "curl[core]", "", "x64")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan( + {unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db); + + Assert::AreEqual(size_t(2), remove_plan.size()); + Assert::AreEqual("cpr", remove_plan[0].spec.name().c_str()); + Assert::AreEqual("curl", remove_plan[1].spec.name().c_str()); + } + + TEST_METHOD(features_depend_core_remove_scheme_2) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("curl", "", "", "x64")); + pghs.push_back(make_status_feature_pgh("curl", "a", "", "x64")); + pghs.push_back(make_status_feature_pgh("curl", "b", "curl[a]", "x64")); + StatusParagraphs status_db(std::move(pghs)); + + auto remove_plan = Dependencies::create_remove_plan( + {unsafe_pspec("curl", Triplet::from_canonical_name("x64"))}, status_db); + + Assert::AreEqual(size_t(1), remove_plan.size()); + Assert::AreEqual("curl", remove_plan[0].spec.name().c_str()); + } + }; + + class UpgradePlanTests : public TestClass<UpgradePlanTests> + { + TEST_METHOD(basic_upgrade_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(2), plan.size()); + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + Assert::AreEqual("a", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].install_action.has_value()); + } + + TEST_METHOD(basic_upgrade_scheme_with_recurse) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b", "a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + spec_map.emplace("b", "a"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(4), plan.size()); + Assert::AreEqual("b", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + Assert::AreEqual("a", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].remove_action.has_value()); + + Assert::AreEqual("a", plan[2].spec().name().c_str()); + Assert::IsTrue(plan[2].install_action.has_value()); + + Assert::AreEqual("b", plan[3].spec().name().c_str()); + Assert::IsTrue(plan[3].install_action.has_value()); + } + + TEST_METHOD(basic_upgrade_scheme_with_bystander) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + spec_map.emplace("b", "a"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(2), plan.size()); + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + Assert::AreEqual("a", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].install_action.has_value()); + } + + TEST_METHOD(basic_upgrade_scheme_with_new_dep) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "b"); + spec_map.emplace("b"); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(3), plan.size()); + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + Assert::AreEqual("b", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].install_action.has_value()); + Assert::AreEqual("a", plan[2].spec().name().c_str()); + Assert::IsTrue(plan[2].install_action.has_value()); + } + + TEST_METHOD(basic_upgrade_scheme_with_features) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_feature_pgh("a", "a1")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + features_check(&plan[1], "a", {"core", "a1"}); + } + + TEST_METHOD(basic_upgrade_scheme_with_new_default_feature) + { + // only core of package "a" is installed + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + // a1 was added as a default feature and should be installed in upgrade + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}, {"a1"}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + features_check(&plan[1], "a", {"core", "a1"}); + } + + TEST_METHOD(basic_upgrade_scheme_with_self_features) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_feature_pgh("a", "a1", "")); + pghs.push_back(make_status_feature_pgh("a", "a2", "a[a1]")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}, {"a2", "a[a1]"}}); + + Dependencies::MapPortFileProvider provider(spec_map.map); + Dependencies::PackageGraph graph(provider, status_db); + + graph.upgrade(spec_a); + + auto plan = graph.serialize(); + + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("a", plan[0].spec().name().c_str()); + Assert::IsTrue(plan[0].remove_action.has_value()); + + Assert::AreEqual("a", plan[1].spec().name().c_str()); + Assert::IsTrue(plan[1].install_action.has_value()); + Assert::IsTrue(plan[1].install_action.get()->feature_list == std::set<std::string>{"core", "a1", "a2"}); + } + }; + + class ExportPlanTests : public TestClass<ExportPlanTests> + { + TEST_METHOD(basic_export_scheme) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + Assert::AreEqual(size_t(1), plan.size()); + Assert::AreEqual("a", plan[0].spec.name().c_str()); + Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + } + + TEST_METHOD(basic_export_scheme_with_recurse) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b", "a")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + auto spec_b = spec_map.emplace("b", "a"); + + auto plan = Dependencies::create_export_plan({spec_b}, status_db); + + Assert::AreEqual(size_t(2), plan.size()); + Assert::AreEqual("a", plan[0].spec.name().c_str()); + Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + + Assert::AreEqual("b", plan[1].spec.name().c_str()); + Assert::IsTrue(plan[1].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + } + + TEST_METHOD(basic_export_scheme_with_bystander) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_pgh("b")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + auto spec_b = spec_map.emplace("b", "a"); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + Assert::AreEqual(size_t(1), plan.size()); + Assert::AreEqual("a", plan[0].spec.name().c_str()); + Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + } + + TEST_METHOD(basic_export_scheme_with_missing) + { + StatusParagraphs status_db; + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a"); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + Assert::AreEqual(size_t(1), plan.size()); + Assert::AreEqual("a", plan[0].spec.name().c_str()); + Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::NOT_BUILT); + } + + TEST_METHOD(basic_export_scheme_with_features) + { + std::vector<std::unique_ptr<StatusParagraph>> pghs; + pghs.push_back(make_status_pgh("b")); + pghs.push_back(make_status_pgh("a")); + pghs.push_back(make_status_feature_pgh("a", "a1", "b[core]")); + StatusParagraphs status_db(std::move(pghs)); + + PackageSpecMap spec_map; + auto spec_a = spec_map.emplace("a", "", {{"a1", ""}}); + + auto plan = Dependencies::create_export_plan({spec_a}, status_db); + + Assert::AreEqual(size_t(2), plan.size()); + + Assert::AreEqual("b", plan[0].spec.name().c_str()); + Assert::IsTrue(plan[0].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + + Assert::AreEqual("a", plan[1].spec.name().c_str()); + Assert::IsTrue(plan[1].plan_type == Dependencies::ExportPlanType::ALREADY_BUILT); + } + }; +} diff --git a/toolsrc/src/tests.statusparagraphs.cpp b/toolsrc/src/tests.statusparagraphs.cpp new file mode 100644 index 000000000..fa0d54fac --- /dev/null +++ b/toolsrc/src/tests.statusparagraphs.cpp @@ -0,0 +1,115 @@ +#include "tests.pch.h" + +#include <tests.utils.h> + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +using namespace vcpkg; +using namespace vcpkg::Paragraphs; + +namespace UnitTest1 +{ + class StatusParagraphsTests : public TestClass<StatusParagraphsTests> + { + TEST_METHOD(find_installed) + { + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed +)"); + Assert::IsTrue(!!pghs); + if (!pghs) return; + + StatusParagraphs status_db(Util::fmap( + *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); + Assert::IsTrue(it != status_db.end()); + } + + TEST_METHOD(find_not_installed) + { + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: purge ok not-installed +)"); + Assert::IsTrue(!!pghs); + if (!pghs) return; + + StatusParagraphs status_db(Util::fmap( + *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); + Assert::IsTrue(it == status_db.end()); + } + + TEST_METHOD(find_with_feature_packages) + { + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed + +Package: ffmpeg +Feature: openssl +Depends: openssl +Architecture: x64-windows +Multi-Arch: same +Description: +Status: purge ok not-installed +)"); + Assert::IsTrue(!!pghs); + if (!pghs) return; + + StatusParagraphs status_db(Util::fmap( + *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + auto it = status_db.find_installed(unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS)); + Assert::IsTrue(it != status_db.end()); + + // Feature "openssl" is not installed and should not be found + auto it1 = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"}); + Assert::IsTrue(it1 == status_db.end()); + } + + TEST_METHOD(find_for_feature_packages) + { + auto pghs = parse_paragraphs(R"( +Package: ffmpeg +Version: 3.3.3 +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed + +Package: ffmpeg +Feature: openssl +Depends: openssl +Architecture: x64-windows +Multi-Arch: same +Description: +Status: install ok installed +)"); + Assert::IsTrue(!!pghs); + if (!pghs) return; + + StatusParagraphs status_db(Util::fmap( + *pghs.get(), [](RawParagraph& rpgh) { return std::make_unique<StatusParagraph>(std::move(rpgh)); })); + + // Feature "openssl" is installed and should therefore be found + auto it = status_db.find_installed({unsafe_pspec("ffmpeg", Triplet::X64_WINDOWS), "openssl"}); + Assert::IsTrue(it != status_db.end()); + } + }; +} diff --git a/toolsrc/src/tests.update.cpp b/toolsrc/src/tests.update.cpp new file mode 100644 index 000000000..b6e487c17 --- /dev/null +++ b/toolsrc/src/tests.update.cpp @@ -0,0 +1,106 @@ +#include "tests.pch.h" + +#include <tests.utils.h> + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +using namespace vcpkg; +using namespace vcpkg::Update; + +namespace UnitTest1 +{ + using Pgh = std::vector<std::unordered_map<std::string, std::string>>; + + class UpdateTests : public TestClass<UpdateTests> + { + TEST_METHOD(find_outdated_packages_basic) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFile> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + map.emplace("a", std::move(*scf)); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + Assert::AreEqual(size_t(1), pkgs.size()); + Assert::AreEqual("2", pkgs[0].version_diff.left.to_string().c_str()); + Assert::AreEqual("0", pkgs[0].version_diff.right.to_string().c_str()); + } + + TEST_METHOD(find_outdated_packages_features) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + status_paragraphs.push_back(make_status_feature_pgh("a", "b")); + status_paragraphs.back()->package.version = "2"; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFile> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + map.emplace("a", std::move(*scf)); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + Assert::AreEqual(size_t(1), pkgs.size()); + Assert::AreEqual("2", pkgs[0].version_diff.left.to_string().c_str()); + Assert::AreEqual("0", pkgs[0].version_diff.right.to_string().c_str()); + } + + TEST_METHOD(find_outdated_packages_features_2) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + status_paragraphs.push_back(make_status_feature_pgh("a", "b")); + status_paragraphs.back()->package.version = "0"; + status_paragraphs.back()->state = InstallState::NOT_INSTALLED; + status_paragraphs.back()->want = Want::PURGE; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFile> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "0"}}})); + map.emplace("a", std::move(*scf)); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + Assert::AreEqual(size_t(1), pkgs.size()); + Assert::AreEqual("2", pkgs[0].version_diff.left.to_string().c_str()); + Assert::AreEqual("0", pkgs[0].version_diff.right.to_string().c_str()); + } + + TEST_METHOD(find_outdated_packages_none) + { + std::vector<std::unique_ptr<StatusParagraph>> status_paragraphs; + status_paragraphs.push_back(make_status_pgh("a")); + status_paragraphs.back()->package.version = "2"; + + StatusParagraphs status_db(std::move(status_paragraphs)); + + std::unordered_map<std::string, SourceControlFile> map; + auto scf = unwrap(SourceControlFile::parse_control_file(Pgh{{{"Source", "a"}, {"Version", "2"}}})); + map.emplace("a", std::move(*scf)); + Dependencies::MapPortFileProvider provider(map); + + auto pkgs = SortedVector<OutdatedPackage>(Update::find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + Assert::AreEqual(size_t(0), pkgs.size()); + } + }; +} diff --git a/toolsrc/src/tests.utils.cpp b/toolsrc/src/tests.utils.cpp new file mode 100644 index 000000000..ac391f559 --- /dev/null +++ b/toolsrc/src/tests.utils.cpp @@ -0,0 +1,42 @@ +#include "tests.pch.h" + +#include "tests.utils.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace vcpkg; + +std::unique_ptr<StatusParagraph> make_status_pgh(const char* name, + const char* depends, + const char* default_features, + const char* triplet) +{ + using Pgh = std::unordered_map<std::string, std::string>; + return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, + {"Version", "1"}, + {"Architecture", triplet}, + {"Multi-Arch", "same"}, + {"Depends", depends}, + {"Default-Features", default_features}, + {"Status", "install ok installed"}}); +} +std::unique_ptr<StatusParagraph> make_status_feature_pgh(const char* name, + const char* feature, + const char* depends, + const char* triplet) +{ + using Pgh = std::unordered_map<std::string, std::string>; + return std::make_unique<StatusParagraph>(Pgh{{"Package", name}, + {"Version", "1"}, + {"Feature", feature}, + {"Architecture", triplet}, + {"Multi-Arch", "same"}, + {"Depends", depends}, + {"Status", "install ok installed"}}); +} + +PackageSpec unsafe_pspec(std::string name, Triplet t) +{ + auto m_ret = PackageSpec::from_name_and_triplet(name, t); + Assert::IsTrue(m_ret.has_value()); + return m_ret.value_or_exit(VCPKG_LINE_INFO); +} diff --git a/toolsrc/src/tests_arguments.cpp b/toolsrc/src/tests_arguments.cpp deleted file mode 100644 index 624fbb910..000000000 --- a/toolsrc/src/tests_arguments.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "CppUnitTest.h"
-#include "VcpkgCmdArguments.h"
-
-#pragma comment(lib, "version")
-#pragma comment(lib, "winhttp")
-
-using namespace Microsoft::VisualStudio::CppUnitTestFramework;
-
-using namespace vcpkg;
-
-namespace UnitTest1
-{
- class ArgumentTests : public TestClass<ArgumentTests>
- {
- TEST_METHOD(create_from_arg_sequence_options_lower)
- {
- std::vector<std::string> t = {"--vcpkg-root", "C:\\vcpkg", "--debug", "--sendmetrics", "--printmetrics"};
- auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
- Assert::AreEqual("C:\\vcpkg", v.vcpkg_root_dir.get()->c_str());
- Assert::IsTrue(v.debug && *v.debug.get());
- Assert::IsTrue(v.sendmetrics && v.sendmetrics.get());
- Assert::IsTrue(v.printmetrics && *v.printmetrics.get());
- }
-
- TEST_METHOD(create_from_arg_sequence_options_upper)
- {
- std::vector<std::string> t = {"--VCPKG-ROOT", "C:\\vcpkg", "--DEBUG", "--SENDMETRICS", "--PRINTMETRICS"};
- auto v = VcpkgCmdArguments::create_from_arg_sequence(t.data(), t.data() + t.size());
- Assert::AreEqual("C:\\vcpkg", v.vcpkg_root_dir.get()->c_str());
- Assert::IsTrue(v.debug && *v.debug.get());
- Assert::IsTrue(v.sendmetrics && v.sendmetrics.get());
- Assert::IsTrue(v.printmetrics && *v.printmetrics.get());
- }
- };
-}
\ No newline at end of file diff --git a/toolsrc/src/tests_dependencies.cpp b/toolsrc/src/tests_dependencies.cpp deleted file mode 100644 index 3aabed80e..000000000 --- a/toolsrc/src/tests_dependencies.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "CppUnitTest.h" -#include "SourceParagraph.h" -#include "Triplet.h" - -#pragma comment(lib, "version") -#pragma comment(lib, "winhttp") - -using namespace Microsoft::VisualStudio::CppUnitTestFramework; - -using namespace vcpkg; - -namespace UnitTest1 -{ - class DependencyTests : public TestClass<DependencyTests> - { - TEST_METHOD(parse_depends_one) - { - auto v = expand_qualified_dependencies(parse_depends("libA [windows]")); - Assert::AreEqual(size_t(1), v.size()); - Assert::AreEqual("libA", v[0].name.c_str()); - Assert::AreEqual("windows", v[0].qualifier.c_str()); - } - - TEST_METHOD(filter_depends) - { - auto deps = expand_qualified_dependencies(parse_depends("libA [windows], libB, libC [uwp]")); - auto v = filter_dependencies(deps, Triplet::X64_WINDOWS); - Assert::AreEqual(size_t(2), v.size()); - Assert::AreEqual("libA", v[0].c_str()); - Assert::AreEqual("libB", v[1].c_str()); - - auto v2 = filter_dependencies(deps, Triplet::ARM_UWP); - Assert::AreEqual(size_t(2), v.size()); - Assert::AreEqual("libB", v2[0].c_str()); - Assert::AreEqual("libC", v2[1].c_str()); - } - }; -} diff --git a/toolsrc/src/triplet.cpp b/toolsrc/src/triplet.cpp deleted file mode 100644 index ff41ce77d..000000000 --- a/toolsrc/src/triplet.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "pch.h" - -#include "Triplet.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Strings.h" - -namespace vcpkg -{ - const Triplet Triplet::X86_WINDOWS = from_canonical_name("x86-windows"); - const Triplet Triplet::X64_WINDOWS = from_canonical_name("x64-windows"); - const Triplet Triplet::X86_UWP = from_canonical_name("x86-uwp"); - const Triplet Triplet::X64_UWP = from_canonical_name("x64-uwp"); - const Triplet Triplet::ARM_UWP = from_canonical_name("arm-uwp"); - - bool operator==(const Triplet& left, const Triplet& right) - { - return left.canonical_name() == right.canonical_name(); - } - - bool operator!=(const Triplet& left, const Triplet& right) { return !(left == right); } - - Triplet Triplet::from_canonical_name(const std::string& triplet_as_string) - { - const std::string s(Strings::ascii_to_lowercase(triplet_as_string)); - auto it = std::find(s.cbegin(), s.cend(), '-'); - Checks::check_exit(VCPKG_LINE_INFO, it != s.cend(), "Invalid triplet: %s", triplet_as_string); - - Triplet t; - t.m_canonical_name = s; - return t; - } - - const std::string& Triplet::canonical_name() const { return this->m_canonical_name; } - - const std::string& Triplet::to_string() const { return this->m_canonical_name; } -} diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index 154aefd0a..ac2eec876 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -1,77 +1,148 @@ +#if defined(_WIN32) #define WIN32_LEAN_AND_MEAN #include <Windows.h> -#include "Paragraphs.h" -#include "metrics.h" -#include "vcpkg_Chrono.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Files.h" -#include "vcpkg_Input.h" -#include "vcpkg_Strings.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" -#include <Shlobj.h> +#pragma warning(push) +#pragma warning(disable : 4768) +#include <ShlObj.h> +#pragma warning(pop) +#else +#include <unistd.h> +#endif + +#include <vcpkg/base/chrono.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/metrics.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/userconfig.h> +#include <vcpkg/vcpkglib.h> + #include <cassert> #include <fstream> #include <memory> +#include <random> + +#pragma comment(lib, "ole32") +#pragma comment(lib, "shell32") using namespace vcpkg; void invalid_command(const std::string& cmd) { System::println(System::Color::error, "invalid command: %s", cmd); - Commands::Help::print_usage(); + Help::print_usage(); Checks::exit_fail(VCPKG_LINE_INFO); } static void inner(const VcpkgCmdArguments& args) { - Metrics::track_property("command", args.command); + Metrics::g_metrics.lock()->track_property("command", args.command); if (args.command.empty()) { - Commands::Help::print_usage(); + Help::print_usage(); Checks::exit_fail(VCPKG_LINE_INFO); } - if (auto command_function = Commands::find(args.command, Commands::get_available_commands_type_c())) + static const auto find_command = [&](auto&& commands) { + auto it = Util::find_if(commands, [&](auto&& commandc) { + return Strings::case_insensitive_ascii_equals(commandc.name, args.command); + }); + using std::end; + if (it != end(commands)) + { + return &*it; + } + else + return static_cast<decltype(&*it)>(nullptr); + }; + + if (const auto command_function = find_command(Commands::get_available_commands_type_c())) { - return command_function(args); + return command_function->function(args); } fs::path vcpkg_root_dir; if (args.vcpkg_root_dir != nullptr) { - vcpkg_root_dir = fs::stdfs::absolute(Strings::to_utf16(*args.vcpkg_root_dir)); + vcpkg_root_dir = fs::stdfs::absolute(fs::u8path(*args.vcpkg_root_dir)); } else { - const Optional<std::wstring> vcpkg_root_dir_env = System::get_environment_variable(L"VCPKG_ROOT"); - if (auto v = vcpkg_root_dir_env.get()) + const auto vcpkg_root_dir_env = System::get_environment_variable("VCPKG_ROOT"); + if (const auto v = vcpkg_root_dir_env.get()) { vcpkg_root_dir = fs::stdfs::absolute(*v); } else { - vcpkg_root_dir = Files::get_real_filesystem().find_file_recursively_up( - fs::stdfs::absolute(System::get_exe_path_of_current_process()), ".vcpkg-root"); + const fs::path current_path = fs::stdfs::current_path(); + vcpkg_root_dir = Files::get_real_filesystem().find_file_recursively_up(current_path, ".vcpkg-root"); + + if (vcpkg_root_dir.empty()) + { + vcpkg_root_dir = Files::get_real_filesystem().find_file_recursively_up( + fs::stdfs::absolute(System::get_exe_path_of_current_process()), ".vcpkg-root"); + } } } Checks::check_exit(VCPKG_LINE_INFO, !vcpkg_root_dir.empty(), "Error: Could not detect vcpkg-root."); - const Expected<VcpkgPaths> expected_paths = VcpkgPaths::create(vcpkg_root_dir); + Debug::println("Using vcpkg-root: %s", vcpkg_root_dir.u8string()); + + auto default_vs_path = System::get_environment_variable("VCPKG_DEFAULT_VS_PATH").value_or(""); + + const Expected<VcpkgPaths> expected_paths = VcpkgPaths::create(vcpkg_root_dir, default_vs_path); Checks::check_exit(VCPKG_LINE_INFO, - !expected_paths.error_code(), + !expected_paths.error(), "Error: Invalid vcpkg root directory %s: %s", vcpkg_root_dir.string(), - expected_paths.error_code().message()); + expected_paths.error().message()); const VcpkgPaths paths = expected_paths.value_or_exit(VCPKG_LINE_INFO); - int exit_code = _wchdir(paths.root.c_str()); + +#if defined(_WIN32) + const int exit_code = _wchdir(paths.root.c_str()); +#else + const int exit_code = chdir(paths.root.c_str()); +#endif Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Changing the working dir failed"); - if (auto command_function = Commands::find(args.command, Commands::get_available_commands_type_b())) + if (args.command != "autocomplete") + { + Commands::Version::warn_if_vcpkg_version_mismatch(paths); + std::string surveydate = *GlobalState::g_surveydate.lock(); + auto maybe_surveydate = Chrono::CTime::parse(surveydate); + if (auto p_surveydate = maybe_surveydate.get()) + { + auto delta = std::chrono::system_clock::now() - p_surveydate->to_time_point(); + // 24 hours/day * 30 days/month + if (std::chrono::duration_cast<std::chrono::hours>(delta).count() > 24 * 30) + { + std::default_random_engine generator( + static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count())); + std::uniform_int_distribution<int> distribution(1, 4); + + if (distribution(generator) == 1) + { + Metrics::g_metrics.lock()->track_property("surveyprompt", "true"); + System::println( + System::Color::success, + "Your feedback is important to improve Vcpkg! Please take 3 minutes to complete our survey " + "by running: vcpkg contact --survey"); + } + } + } + } + + if (const auto command_function = find_command(Commands::get_available_commands_type_b())) { - return command_function(args, paths); + return command_function->function(args, paths); } Triplet default_triplet; @@ -81,92 +152,84 @@ static void inner(const VcpkgCmdArguments& args) } else { - const Optional<std::wstring> vcpkg_default_triplet_env = - System::get_environment_variable(L"VCPKG_DEFAULT_TRIPLET"); - if (auto v = vcpkg_default_triplet_env.get()) + const auto vcpkg_default_triplet_env = System::get_environment_variable("VCPKG_DEFAULT_TRIPLET"); + if (const auto v = vcpkg_default_triplet_env.get()) { - default_triplet = Triplet::from_canonical_name(Strings::to_utf8(*v)); + default_triplet = Triplet::from_canonical_name(*v); } else { +#if defined(_WIN32) default_triplet = Triplet::X86_WINDOWS; +#elif defined(__APPLE__) + default_triplet = Triplet::from_canonical_name("x64-osx"); +#elif defined(__FreeBSD__) + default_triplet = Triplet::from_canonical_name("x64-freebsd"); +#else + default_triplet = Triplet::from_canonical_name("x64-linux"); +#endif } } Input::check_triplet(default_triplet, paths); - if (auto command_function = Commands::find(args.command, Commands::get_available_commands_type_a())) + if (const auto command_function = find_command(Commands::get_available_commands_type_a())) { - return command_function(args, paths, default_triplet); + return command_function->function(args, paths, default_triplet); } return invalid_command(args.command); } -static void loadConfig() +static void load_config() { - 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); - } + auto& fs = Files::get_real_filesystem(); - try - { - auto maybe_pghs = Paragraphs::get_paragraphs(Files::get_real_filesystem(), localappdata / "vcpkg" / "config"); - if (auto p_pghs = maybe_pghs.get()) - { - const auto& pghs = *p_pghs; + auto config = UserConfig::try_read_data(fs); - std::unordered_map<std::string, std::string> keys; - if (pghs.size() > 0) keys = pghs[0]; + bool write_config = false; - for (size_t x = 1; x < pghs.size(); ++x) - { - for (auto&& p : pghs[x]) - keys.insert(p); - } + // config file not found, could not be read, or invalid + if (config.user_id.empty() || config.user_time.empty()) + { + ::vcpkg::Metrics::Metrics::init_user_information(config.user_id, config.user_time); + write_config = true; + } - auto user_id = keys["User-Id"]; - auto user_time = keys["User-Since"]; - if (!user_id.empty() && !user_time.empty()) - { - Metrics::set_user_information(user_id, user_time); - return; - } - } +#if defined(_WIN32) + if (config.user_mac.empty()) + { + config.user_mac = Metrics::get_MAC_user(); + write_config = true; } - catch (...) +#endif + { + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->set_user_information(config.user_id, config.user_time); +#if defined(_WIN32) + locked_metrics->track_property("user_mac", config.user_mac); +#endif } - // config file not found, could not be read, or invalid - std::string user_id, user_time; - Metrics::init_user_information(user_id, user_time); - Metrics::set_user_information(user_id, user_time); - try + if (config.last_completed_survey.empty()) { - std::error_code ec; - auto& fs = Files::get_real_filesystem(); - fs.create_directory(localappdata / "vcpkg", ec); - fs.write_contents(localappdata / "vcpkg" / "config", - Strings::format("User-Id: %s\n" - "User-Since: %s\n", - user_id, - user_time)); + config.last_completed_survey = config.user_time; } - catch (...) + + GlobalState::g_surveydate.lock()->assign(config.last_completed_survey); + + if (write_config) { + config.try_write_data(fs); } } +#if defined(_WIN32) static std::string trim_path_from_command_line(const std::string& full_command_line) { Checks::check_exit( - VCPKG_LINE_INFO, full_command_line.size() > 0, "Internal failure - cannot have empty command line"); + VCPKG_LINE_INFO, !full_command_line.empty(), "Internal failure - cannot have empty command line"); if (full_command_line[0] == '"') { @@ -183,35 +246,56 @@ static std::string trim_path_from_command_line(const std::string& full_command_l ++it; return std::string(it, full_command_line.cend()); } +#endif -static ElapsedTime g_timer; - +#if defined(_WIN32) int wmain(const int argc, const wchar_t* const* const argv) +#else +int main(const int argc, const char* const* const argv) +#endif { if (argc == 0) std::abort(); - g_timer = ElapsedTime::create_started(); - atexit([]() { - auto elapsed_us = g_timer.microseconds(); - Metrics::track_metric("elapsed_us", elapsed_us); - g_debugging = false; - Metrics::flush(); - }); + *GlobalState::timer.lock() = Chrono::ElapsedTimer::create_started(); - Metrics::track_property("version", Commands::Version::version()); +#if defined(_WIN32) + GlobalState::g_init_console_cp = GetConsoleCP(); + GlobalState::g_init_console_output_cp = GetConsoleOutputCP(); + + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); const std::string trimmed_command_line = trim_path_from_command_line(Strings::to_utf8(GetCommandLineW())); - Metrics::track_property("cmdline", trimmed_command_line); - loadConfig(); - Metrics::track_property("sqmuser", Metrics::get_SQM_user()); +#endif + + { + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->track_property("version", Commands::Version::version()); +#if defined(_WIN32) + locked_metrics->track_property("cmdline", trimmed_command_line); +#endif + } + load_config(); + + const auto vcpkg_feature_flags_env = System::get_environment_variable("VCPKG_FEATURE_FLAGS"); + if (const auto v = vcpkg_feature_flags_env.get()) + { + auto flags = Strings::split(*v, ","); + if (std::find(flags.begin(), flags.end(), "binarycaching") != flags.end()) GlobalState::g_binary_caching = true; + } const VcpkgCmdArguments args = VcpkgCmdArguments::create_from_command_line(argc, argv); - if (auto p = args.printmetrics.get()) Metrics::set_print_metrics(*p); - if (auto p = args.sendmetrics.get()) Metrics::set_send_metrics(*p); - if (auto p = args.debug.get()) g_debugging = *p; + if (const auto p = args.featurepackages.get()) GlobalState::feature_packages = *p; + if (const auto p = args.binarycaching.get()) GlobalState::g_binary_caching = *p; + + if (const auto p = args.printmetrics.get()) Metrics::g_metrics.lock()->set_print_metrics(*p); + if (const auto p = args.sendmetrics.get()) Metrics::g_metrics.lock()->set_send_metrics(*p); + if (const auto p = args.debug.get()) GlobalState::debugging = *p; - if (g_debugging) + Checks::register_console_ctrl_handler(); + + if (GlobalState::debugging) { inner(args); Checks::exit_fail(VCPKG_LINE_INFO); @@ -231,7 +315,7 @@ int wmain(const int argc, const wchar_t* const* const argv) { exc_msg = "unknown error(...)"; } - Metrics::track_property("error", exc_msg); + Metrics::g_metrics.lock()->track_property("error", exc_msg); fflush(stdout); System::print("vcpkg.exe has crashed.\n" @@ -247,6 +331,12 @@ int wmain(const int argc, const wchar_t* const* const argv) exc_msg); fflush(stdout); for (int x = 0; x < argc; ++x) + { +#if defined(_WIN32) System::println("%s|", Strings::to_utf8(argv[x])); +#else + System::println("%s|", argv[x]); +#endif + } fflush(stdout); } diff --git a/toolsrc/src/vcpkg/base/checks.cpp b/toolsrc/src/vcpkg/base/checks.cpp new file mode 100644 index 000000000..2ac5f9a15 --- /dev/null +++ b/toolsrc/src/vcpkg/base/checks.cpp @@ -0,0 +1,96 @@ +#include "pch.h" + +#include <vcpkg/globalstate.h> +#include <vcpkg/metrics.h> + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/system.h> + +namespace vcpkg::Checks +{ + [[noreturn]] static void cleanup_and_exit(const int exit_code) + { + static std::atomic<bool> have_entered{false}; + if (have_entered) std::terminate(); + have_entered = true; + + const auto elapsed_us = GlobalState::timer.lock()->microseconds(); + + Debug::println("Exiting after %d us", static_cast<int>(elapsed_us)); + + auto metrics = Metrics::g_metrics.lock(); + metrics->track_metric("elapsed_us", elapsed_us); + GlobalState::debugging = false; + metrics->flush(); + +#if defined(_WIN32) + SetConsoleCP(GlobalState::g_init_console_cp); + SetConsoleOutputCP(GlobalState::g_init_console_output_cp); +#endif + + fflush(nullptr); + +#if defined(_WIN32) + ::TerminateProcess(::GetCurrentProcess(), exit_code); +#else + std::exit(exit_code); +#endif + } + +#if defined(_WIN32) + static BOOL ctrl_handler(DWORD fdw_ctrl_type) + { + { + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->track_property("CtrlHandler", std::to_string(fdw_ctrl_type)); + locked_metrics->track_property("error", "CtrlHandler was fired."); + } + cleanup_and_exit(EXIT_FAILURE); + } + + void register_console_ctrl_handler() + { + SetConsoleCtrlHandler(reinterpret_cast<PHANDLER_ROUTINE>(ctrl_handler), TRUE); + } +#else + void register_console_ctrl_handler() {} +#endif + void unreachable(const LineInfo& line_info) + { + System::println(System::Color::error, "Error: Unreachable code was reached"); + System::println(System::Color::error, line_info.to_string()); // Always print line_info here +#ifndef NDEBUG + std::abort(); +#else + cleanup_and_exit(EXIT_FAILURE); +#endif + } + + void exit_with_code(const LineInfo& line_info, const int exit_code) + { + Debug::println(System::Color::error, line_info.to_string()); + cleanup_and_exit(exit_code); + } + + void exit_with_message(const LineInfo& line_info, const CStringView error_message) + { + System::println(System::Color::error, error_message); + exit_fail(line_info); + } + + void check_exit(const LineInfo& line_info, bool expression) + { + if (!expression) + { + exit_fail(line_info); + } + } + + void check_exit(const LineInfo& line_info, bool expression, const CStringView error_message) + { + if (!expression) + { + exit_with_message(line_info, error_message); + } + } +} diff --git a/toolsrc/src/vcpkg/base/chrono.cpp b/toolsrc/src/vcpkg/base/chrono.cpp new file mode 100644 index 000000000..2a76f5df0 --- /dev/null +++ b/toolsrc/src/vcpkg/base/chrono.cpp @@ -0,0 +1,129 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/chrono.h> + +namespace vcpkg::Chrono +{ + static std::string format_time_userfriendly(const std::chrono::nanoseconds& nanos) + { + using std::chrono::duration_cast; + using std::chrono::hours; + using std::chrono::microseconds; + using std::chrono::milliseconds; + using std::chrono::minutes; + using std::chrono::nanoseconds; + using std::chrono::seconds; + + const auto nanos_as_double = static_cast<double>(nanos.count()); + + if (duration_cast<hours>(nanos) > hours()) + { + const auto t = nanos_as_double / duration_cast<nanoseconds>(hours(1)).count(); + return Strings::format("%.4g h", t); + } + + if (duration_cast<minutes>(nanos) > minutes()) + { + const auto t = nanos_as_double / duration_cast<nanoseconds>(minutes(1)).count(); + return Strings::format("%.4g min", t); + } + + if (duration_cast<seconds>(nanos) > seconds()) + { + const auto t = nanos_as_double / duration_cast<nanoseconds>(seconds(1)).count(); + return Strings::format("%.4g s", t); + } + + if (duration_cast<milliseconds>(nanos) > milliseconds()) + { + const auto t = nanos_as_double / duration_cast<nanoseconds>(milliseconds(1)).count(); + return Strings::format("%.4g ms", t); + } + + if (duration_cast<microseconds>(nanos) > microseconds()) + { + const auto t = nanos_as_double / duration_cast<nanoseconds>(microseconds(1)).count(); + return Strings::format("%.4g us", t); + } + + return Strings::format("%.4g ns", nanos_as_double); + } + + ElapsedTimer ElapsedTimer::create_started() + { + ElapsedTimer t; + t.m_start_tick = std::chrono::high_resolution_clock::now(); + return t; + } + + std::string ElapsedTime::to_string() const { return format_time_userfriendly(as<std::chrono::nanoseconds>()); } + + std::string ElapsedTimer::to_string() const { return elapsed().to_string(); } + + Optional<CTime> CTime::get_current_date_time() + { + CTime ret; + +#if defined(_WIN32) + struct _timeb timebuffer; + + _ftime_s(&timebuffer); + + const errno_t err = gmtime_s(&ret.m_tm, &timebuffer.time); + + if (err) + { + return nullopt; + } +#else + time_t now = {0}; + time(&now); + auto null_if_failed = gmtime_r(&now, &ret.m_tm); + if (null_if_failed == nullptr) + { + return nullopt; + } +#endif + + return ret; + } + + Optional<CTime> CTime::parse(CStringView str) + { + CTime ret; + const auto assigned = +#if defined(_WIN32) + sscanf_s +#else + sscanf +#endif + (str.c_str(), + "%d-%d-%dT%d:%d:%d.", + &ret.m_tm.tm_year, + &ret.m_tm.tm_mon, + &ret.m_tm.tm_mday, + &ret.m_tm.tm_hour, + &ret.m_tm.tm_min, + &ret.m_tm.tm_sec); + if (assigned != 6) return nullopt; + if (ret.m_tm.tm_year < 1900) return nullopt; + ret.m_tm.tm_year -= 1900; + if (ret.m_tm.tm_mon < 1) return nullopt; + ret.m_tm.tm_mon -= 1; + mktime(&ret.m_tm); + return ret; + } + + std::string CTime::to_string() const + { + std::array<char, 80> date{}; + strftime(&date[0], date.size(), "%Y-%m-%dT%H:%M:%S.0Z", &m_tm); + return &date[0]; + } + std::chrono::system_clock::time_point CTime::to_time_point() const + { + const time_t t = mktime(&m_tm); + return std::chrono::system_clock::from_time_t(t); + } +} diff --git a/toolsrc/src/coff_file_reader.cpp b/toolsrc/src/vcpkg/base/cofffilereader.cpp index 467fc9a64..2c09e2c19 100644 --- a/toolsrc/src/coff_file_reader.cpp +++ b/toolsrc/src/vcpkg/base/cofffilereader.cpp @@ -1,12 +1,14 @@ #include "pch.h" -#include "coff_file_reader.h" -#include "vcpkg_Checks.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/base/cofffilereader.h> +#include <vcpkg/base/stringliteral.h> using namespace std; -namespace vcpkg::COFFFileReader +namespace vcpkg::CoffFileReader { +#if defined(_WIN32) template<class T> static T reinterpret_bytes(const char* data) { @@ -24,7 +26,7 @@ namespace vcpkg::COFFFileReader template<class T> static T peek_value_from_stream(fstream& fs) { - fpos_t original_pos = fs.tellg().seekpos(); + const std::streampos original_pos = fs.tellg(); T data; fs.read(reinterpret_cast<char*>(&data), sizeof data); fs.seekg(original_pos); @@ -42,21 +44,21 @@ namespace vcpkg::COFFFileReader actual); } - static void read_and_verify_PE_signature(fstream& fs) + static void read_and_verify_pe_signature(fstream& fs) { - static const size_t OFFSET_TO_PE_SIGNATURE_OFFSET = 0x3c; + static constexpr size_t OFFSET_TO_PE_SIGNATURE_OFFSET = 0x3c; - static const char* PE_SIGNATURE = "PE\0\0"; - static const size_t PE_SIGNATURE_SIZE = 4; + static constexpr StringLiteral PE_SIGNATURE = "PE\0\0"; + static constexpr size_t PE_SIGNATURE_SIZE = 4; fs.seekg(OFFSET_TO_PE_SIGNATURE_OFFSET, ios_base::beg); - const int32_t offset_to_PE_signature = read_value_from_stream<int32_t>(fs); + const auto offset_to_pe_signature = read_value_from_stream<int32_t>(fs); - fs.seekg(offset_to_PE_signature); + fs.seekg(offset_to_pe_signature); char signature[PE_SIGNATURE_SIZE]; fs.read(signature, PE_SIGNATURE_SIZE); - verify_equal_strings(VCPKG_LINE_INFO, PE_SIGNATURE, signature, PE_SIGNATURE_SIZE, "PE_SIGNATURE"); - fs.seekg(offset_to_PE_signature + PE_SIGNATURE_SIZE, ios_base::beg); + verify_equal_strings(VCPKG_LINE_INFO, PE_SIGNATURE.c_str(), signature, PE_SIGNATURE_SIZE, "PE_SIGNATURE"); + fs.seekg(offset_to_pe_signature + PE_SIGNATURE_SIZE, ios_base::beg); } static fpos_t align_to_size(const uint64_t unaligned, const uint64_t alignment_size) @@ -70,7 +72,7 @@ namespace vcpkg::COFFFileReader struct CoffFileHeader { - static const size_t HEADER_SIZE = 20; + static constexpr size_t HEADER_SIZE = 20; static CoffFileHeader read(fstream& fs) { @@ -80,14 +82,14 @@ namespace vcpkg::COFFFileReader return ret; } - MachineType machineType() const + MachineType machine_type() const { - static const size_t MACHINE_TYPE_OFFSET = 0; - static const size_t MACHINE_TYPE_SIZE = 2; + static constexpr size_t MACHINE_TYPE_OFFSET = 0; + static constexpr size_t MACHINE_TYPE_SIZE = 2; std::string machine_field_as_string = data.substr(MACHINE_TYPE_OFFSET, MACHINE_TYPE_SIZE); - const uint16_t machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); - return getMachineType(machine); + const auto machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); + return to_machine_type(machine); } private: @@ -96,13 +98,13 @@ namespace vcpkg::COFFFileReader struct ArchiveMemberHeader { - static const size_t HEADER_SIZE = 60; + static constexpr size_t HEADER_SIZE = 60; static ArchiveMemberHeader read(fstream& fs) { - static const size_t HEADER_END_OFFSET = 58; - static const char* HEADER_END = "`\n"; - static const size_t HEADER_END_SIZE = 2; + static constexpr size_t HEADER_END_OFFSET = 58; + static constexpr StringLiteral HEADER_END = "`\n"; + static constexpr size_t HEADER_END_SIZE = 2; ArchiveMemberHeader ret; ret.data.resize(HEADER_SIZE); @@ -112,7 +114,7 @@ namespace vcpkg::COFFFileReader { const std::string header_end = ret.data.substr(HEADER_END_OFFSET, HEADER_END_SIZE); verify_equal_strings( - VCPKG_LINE_INFO, HEADER_END, header_end.c_str(), HEADER_END_SIZE, "LIB HEADER_END"); + VCPKG_LINE_INFO, HEADER_END.c_str(), header_end.c_str(), HEADER_END_SIZE, "LIB HEADER_END"); } return ret; @@ -120,17 +122,17 @@ namespace vcpkg::COFFFileReader std::string name() const { - static const size_t HEADER_NAME_OFFSET = 0; - static const size_t HEADER_NAME_SIZE = 16; + static constexpr size_t HEADER_NAME_OFFSET = 0; + static constexpr size_t HEADER_NAME_SIZE = 16; return data.substr(HEADER_NAME_OFFSET, HEADER_NAME_SIZE); } uint64_t member_size() const { - static const size_t ALIGNMENT_SIZE = 2; + static constexpr size_t ALIGNMENT_SIZE = 2; - static const size_t HEADER_SIZE_OFFSET = 48; - static const size_t HEADER_SIZE_FIELD_SIZE = 10; + static constexpr size_t HEADER_SIZE_OFFSET = 48; + static constexpr size_t HEADER_SIZE_FIELD_SIZE = 10; const std::string as_string = data.substr(HEADER_SIZE_OFFSET, HEADER_SIZE_FIELD_SIZE); // This is in ASCII decimal representation const uint64_t value = std::strtoull(as_string.c_str(), nullptr, 10); @@ -146,18 +148,19 @@ namespace vcpkg::COFFFileReader { static OffsetsArray read(fstream& fs, const uint32_t offset_count) { - static const size_t OFFSET_WIDTH = 4; + static constexpr uint32_t OFFSET_WIDTH = 4; std::string raw_offsets; - const size_t raw_offset_size = offset_count * OFFSET_WIDTH; + const uint32_t raw_offset_size = offset_count * OFFSET_WIDTH; raw_offsets.resize(raw_offset_size); fs.read(&raw_offsets[0], raw_offset_size); OffsetsArray ret; for (uint32_t i = 0; i < offset_count; ++i) { - const std::string value_as_string = raw_offsets.substr(OFFSET_WIDTH * i, OFFSET_WIDTH * (i + 1)); - const uint32_t value = reinterpret_bytes<uint32_t>(value_as_string.c_str()); + const std::string value_as_string = raw_offsets.substr(OFFSET_WIDTH * static_cast<size_t>(i), + OFFSET_WIDTH * (static_cast<size_t>(i) + 1)); + const auto value = reinterpret_bytes<uint32_t>(value_as_string.c_str()); // Ignore offsets that point to offset 0. See vcpkg github #223 #288 #292 if (value != 0) @@ -176,41 +179,41 @@ namespace vcpkg::COFFFileReader struct ImportHeader { - static const size_t HEADER_SIZE = 20; + static constexpr size_t HEADER_SIZE = 20; static ImportHeader read(fstream& fs) { - static const size_t SIG1_OFFSET = 0; - static const uint16_t SIG1 = static_cast<uint16_t>(MachineType::UNKNOWN); - static const size_t SIG1_SIZE = 2; + static constexpr size_t SIG1_OFFSET = 0; + static constexpr auto SIG1 = static_cast<uint16_t>(MachineType::UNKNOWN); + static constexpr size_t SIG1_SIZE = 2; - static const size_t SIG2_OFFSET = 2; - static const uint16_t SIG2 = 0xFFFF; - static const size_t SIG2_SIZE = 2; + static constexpr size_t SIG2_OFFSET = 2; + static constexpr uint16_t SIG2 = 0xFFFF; + static constexpr size_t SIG2_SIZE = 2; ImportHeader ret; ret.data.resize(HEADER_SIZE); fs.read(&ret.data[0], HEADER_SIZE); const std::string sig1_as_string = ret.data.substr(SIG1_OFFSET, SIG1_SIZE); - const uint16_t sig1 = reinterpret_bytes<uint16_t>(sig1_as_string.c_str()); + const auto sig1 = reinterpret_bytes<uint16_t>(sig1_as_string.c_str()); Checks::check_exit(VCPKG_LINE_INFO, sig1 == SIG1, "Sig1 was incorrect. Expected %s but got %s", SIG1, sig1); const std::string sig2_as_string = ret.data.substr(SIG2_OFFSET, SIG2_SIZE); - const uint16_t sig2 = reinterpret_bytes<uint16_t>(sig2_as_string.c_str()); + const auto sig2 = reinterpret_bytes<uint16_t>(sig2_as_string.c_str()); Checks::check_exit(VCPKG_LINE_INFO, sig2 == SIG2, "Sig2 was incorrect. Expected %s but got %s", SIG2, sig2); return ret; } - MachineType machineType() const + MachineType machine_type() const { - static const size_t MACHINE_TYPE_OFFSET = 6; - static const size_t MACHINE_TYPE_SIZE = 2; + static constexpr size_t MACHINE_TYPE_OFFSET = 6; + static constexpr size_t MACHINE_TYPE_SIZE = 2; std::string machine_field_as_string = data.substr(MACHINE_TYPE_OFFSET, MACHINE_TYPE_SIZE); - const uint16_t machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); - return getMachineType(machine); + const auto machine = reinterpret_bytes<uint16_t>(machine_field_as_string.c_str()); + return to_machine_type(machine); } private: @@ -219,14 +222,14 @@ namespace vcpkg::COFFFileReader static void read_and_verify_archive_file_signature(fstream& fs) { - static const char* FILE_START = "!<arch>\n"; - static const size_t FILE_START_SIZE = 8; + static constexpr StringLiteral FILE_START = "!<arch>\n"; + static constexpr size_t FILE_START_SIZE = 8; - fs.seekg(fs.beg); + fs.seekg(std::fstream::beg); char file_start[FILE_START_SIZE]; fs.read(file_start, FILE_START_SIZE); - verify_equal_strings(VCPKG_LINE_INFO, FILE_START, file_start, FILE_START_SIZE, "LIB FILE_START"); + verify_equal_strings(VCPKG_LINE_INFO, FILE_START.c_str(), file_start, FILE_START_SIZE, "LIB FILE_START"); } DllInfo read_dll(const fs::path& path) @@ -234,9 +237,9 @@ namespace vcpkg::COFFFileReader std::fstream fs(path, std::ios::in | std::ios::binary | std::ios::ate); Checks::check_exit(VCPKG_LINE_INFO, fs.is_open(), "Could not open file %s for reading", path.generic_string()); - read_and_verify_PE_signature(fs); + read_and_verify_pe_signature(fs); CoffFileHeader header = CoffFileHeader::read(fs); - MachineType machine = header.machineType(); + const MachineType machine = header.machine_type(); return {machine}; } @@ -244,7 +247,7 @@ namespace vcpkg::COFFFileReader { void set_to_offset(const fpos_t position) { this->m_absolute_position = position; } - void set_to_current_pos(fstream& fs) { this->m_absolute_position = fs.tellg().seekpos(); } + void set_to_current_pos(fstream& fs) { this->m_absolute_position = fs.tellg(); } void seek_to_marker(fstream& fs) const { fs.seekg(this->m_absolute_position, ios_base::beg); } @@ -277,13 +280,13 @@ namespace vcpkg::COFFFileReader second_linker_member_header.name().substr(0, 2) == "/ ", "Could not find proper second linker member"); // The first 4 bytes contains the number of archive members - const uint32_t archive_member_count = read_value_from_stream<uint32_t>(fs); + const auto archive_member_count = read_value_from_stream<uint32_t>(fs); const OffsetsArray offsets = OffsetsArray::read(fs, archive_member_count); marker.advance_by(ArchiveMemberHeader::HEADER_SIZE + second_linker_member_header.member_size()); marker.seek_to_marker(fs); - bool hasLongnameMemberHeader = peek_value_from_stream<uint16_t>(fs) == 0x2F2F; - if (hasLongnameMemberHeader) + const bool has_longname_member_header = peek_value_from_stream<uint16_t>(fs) == 0x2F2F; + if (has_longname_member_header) { const ArchiveMemberHeader longnames_member_header = ArchiveMemberHeader::read(fs); marker.advance_by(ArchiveMemberHeader::HEADER_SIZE + longnames_member_header.member_size()); @@ -296,13 +299,14 @@ namespace vcpkg::COFFFileReader { marker.set_to_offset(offset + ArchiveMemberHeader::HEADER_SIZE); // Skip the header, no need to read it. marker.seek_to_marker(fs); - const uint16_t first_two_bytes = peek_value_from_stream<uint16_t>(fs); - const bool isImportHeader = getMachineType(first_two_bytes) == MachineType::UNKNOWN; + const auto first_two_bytes = peek_value_from_stream<uint16_t>(fs); + const bool is_import_header = to_machine_type(first_two_bytes) == MachineType::UNKNOWN; const MachineType machine = - isImportHeader ? ImportHeader::read(fs).machineType() : CoffFileHeader::read(fs).machineType(); + is_import_header ? ImportHeader::read(fs).machine_type() : CoffFileHeader::read(fs).machine_type(); machine_types.insert(machine); } return {std::vector<MachineType>(machine_types.cbegin(), machine_types.cend())}; } +#endif } diff --git a/toolsrc/src/vcpkg_Enums.cpp b/toolsrc/src/vcpkg/base/enums.cpp index 51ba9d5dc..aa124f3aa 100644 --- a/toolsrc/src/vcpkg_Enums.cpp +++ b/toolsrc/src/vcpkg/base/enums.cpp @@ -1,7 +1,7 @@ #include "pch.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Enums.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/base/enums.h> namespace vcpkg::Enums { diff --git a/toolsrc/src/vcpkg_Files.cpp b/toolsrc/src/vcpkg/base/files.cpp index 17aa2997a..3d96e834b 100644 --- a/toolsrc/src/vcpkg_Files.cpp +++ b/toolsrc/src/vcpkg/base/files.cpp @@ -1,8 +1,8 @@ #include "pch.h" -#include "vcpkg_Files.h" -#include "vcpkg_System.h" -#include <thread> +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> namespace vcpkg::Files { @@ -15,16 +15,16 @@ namespace vcpkg::Files 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; + return std::make_error_code(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) + if (length == std::streampos(-1)) { - return std::errc::file_too_large; + return std::make_error_code(std::errc::io_error); } std::string output; @@ -39,7 +39,7 @@ namespace vcpkg::Files 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; + return std::make_error_code(std::errc::no_such_file_or_directory); } std::vector<std::string> output; @@ -55,24 +55,27 @@ namespace vcpkg::Files virtual fs::path find_file_recursively_up(const fs::path& starting_dir, const std::string& filename) const override { + static const fs::path UNIX_ROOT = "/"; fs::path current_dir = starting_dir; - for (; !current_dir.empty(); current_dir = current_dir.parent_path()) + for (; !current_dir.empty() && current_dir != UNIX_ROOT; current_dir = current_dir.parent_path()) { const fs::path candidate = current_dir / filename; if (exists(candidate)) { - break; + return current_dir; } } - return current_dir; + return fs::path(); } virtual std::vector<fs::path> get_files_recursive(const fs::path& dir) const override { std::vector<fs::path> ret; - fs::stdfs::recursive_directory_iterator b(dir), e{}; + std::error_code ec; + fs::stdfs::recursive_directory_iterator b(dir, ec), e{}; + if (ec) return ret; for (; b != e; ++b) { ret.push_back(b->path()); @@ -85,7 +88,9 @@ namespace vcpkg::Files { std::vector<fs::path> ret; - fs::stdfs::directory_iterator b(dir), e{}; + std::error_code ec; + fs::stdfs::directory_iterator b(dir, ec), e{}; + if (ec) return ret; for (; b != e; ++b) { ret.push_back(b->path()); @@ -104,6 +109,10 @@ namespace vcpkg::Files output.close(); } + virtual void rename(const fs::path& oldpath, const fs::path& newpath, std::error_code& ec) override + { + fs::stdfs::rename(oldpath, newpath, ec); + } virtual void rename(const fs::path& oldpath, const fs::path& newpath) override { fs::stdfs::rename(oldpath, newpath); @@ -160,15 +169,33 @@ namespace vcpkg::Files { return fs::stdfs::status(path, ec); } - virtual void write_contents(const fs::path& file_path, const std::string& data) override + virtual void write_contents(const fs::path& file_path, const std::string& data, std::error_code& ec) override { + ec.clear(); + FILE* f = nullptr; - auto ec = _wfopen_s(&f, file_path.native().c_str(), L"wb"); - Checks::check_exit(VCPKG_LINE_INFO, ec == 0); - auto count = fwrite(data.data(), sizeof(data[0]), data.size(), f); - fclose(f); +#if defined(_WIN32) + auto err = _wfopen_s(&f, file_path.native().c_str(), L"wb"); +#else + f = fopen(file_path.native().c_str(), "wb"); + int err = f != nullptr ? 0 : 1; +#endif + if (err != 0) + { + ec.assign(err, std::system_category()); + return; + } + + if (f != nullptr) + { + auto count = fwrite(data.data(), sizeof(data[0]), data.size(), f); + fclose(f); - Checks::check_exit(VCPKG_LINE_INFO, count == data.size()); + if (count != data.size()) + { + ec = std::make_error_code(std::errc::no_space_on_device); + } + } } }; @@ -185,11 +212,27 @@ namespace vcpkg::Files void print_paths(const std::vector<fs::path>& paths) { - System::println(""); + System::println(); for (const fs::path& p : paths) { System::println(" %s", p.generic_string()); } - System::println(""); + System::println(); + } + + std::vector<fs::path> find_from_PATH(const std::string& name) + { +#if defined(_WIN32) + const std::string cmd = Strings::format("where.exe %s", name); +#else + const std::string cmd = Strings::format("which %s", name); +#endif + auto out = System::cmd_execute_and_capture_output(cmd); + if (out.exit_code != 0) + { + return {}; + } + + return Util::fmap(Strings::split(out.output, "\n"), [](auto&& s) { return fs::path(s); }); } } diff --git a/toolsrc/src/LineInfo.cpp b/toolsrc/src/vcpkg/base/lineinfo.cpp index d1bf9a4b1..7435ed666 100644 --- a/toolsrc/src/LineInfo.cpp +++ b/toolsrc/src/vcpkg/base/lineinfo.cpp @@ -1,7 +1,7 @@ #include "pch.h" -#include "LineInfo.h" -#include "vcpkg_Strings.h" +#include <vcpkg/base/lineinfo.h> +#include <vcpkg/base/strings.h> namespace vcpkg { diff --git a/toolsrc/src/MachineType.cpp b/toolsrc/src/vcpkg/base/machinetype.cpp index f288855e6..2b7bd5e3a 100644 --- a/toolsrc/src/MachineType.cpp +++ b/toolsrc/src/vcpkg/base/machinetype.cpp @@ -1,13 +1,13 @@ #include "pch.h" -#include "MachineType.h" -#include "vcpkg_Checks.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/base/machinetype.h> namespace vcpkg { - MachineType getMachineType(const uint16_t value) + MachineType to_machine_type(const uint16_t value) { - MachineType t = static_cast<MachineType>(value); + const MachineType t = static_cast<MachineType>(value); switch (t) { case MachineType::UNKNOWN: diff --git a/toolsrc/src/vcpkg/base/strings.cpp b/toolsrc/src/vcpkg/base/strings.cpp new file mode 100644 index 000000000..8d43e7af7 --- /dev/null +++ b/toolsrc/src/vcpkg/base/strings.cpp @@ -0,0 +1,192 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/util.h> + +namespace vcpkg::Strings::details +{ + // To disambiguate between two overloads + static bool is_space(const char c) { return std::isspace(c) != 0; } + + // Avoids C4244 warnings because of char<->int conversion that occur when using std::tolower() + static char tolower_char(const char c) { return static_cast<char>(std::tolower(c)); } + static char toupper_char(const char c) { return static_cast<char>(std::toupper(c)); } + +#if defined(_WIN32) + static _locale_t& c_locale() + { + static _locale_t c_locale_impl = _create_locale(LC_ALL, "C"); + return c_locale_impl; + } +#endif + + std::string format_internal(const char* fmtstr, ...) + { + va_list args; + va_start(args, fmtstr); + +#if defined(_WIN32) + const int sz = _vscprintf_l(fmtstr, c_locale(), args); +#else + const int sz = vsnprintf(nullptr, 0, fmtstr, args); +#endif + Checks::check_exit(VCPKG_LINE_INFO, sz > 0); + + std::string output(sz, '\0'); + +#if defined(_WIN32) + _vsnprintf_s_l(&output.at(0), output.size() + 1, output.size(), fmtstr, c_locale(), args); +#else + va_start(args, fmtstr); + vsnprintf(&output.at(0), output.size() + 1, fmtstr, args); +#endif + va_end(args); + + return output; + } +} + +namespace vcpkg::Strings +{ +#if defined(_WIN32) + std::wstring to_utf16(const CStringView& s) + { + std::wstring output; + const size_t size = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, nullptr, 0); + if (size == 0) return output; + output.resize(size - 1); + MultiByteToWideChar(CP_UTF8, 0, s.c_str(), -1, output.data(), static_cast<int>(size) - 1); + return output; + } +#endif + +#if defined(_WIN32) + std::string to_utf8(const wchar_t* w) + { + std::string output; + const size_t size = WideCharToMultiByte(CP_UTF8, 0, w, -1, nullptr, 0, nullptr, nullptr); + if (size == 0) return output; + output.resize(size - 1); + WideCharToMultiByte(CP_UTF8, 0, w, -1, output.data(), static_cast<int>(size) - 1, nullptr, nullptr); + return output; + } +#endif + + std::string escape_string(const CStringView& s, char char_to_escape, char escape_char) + { + std::string ret = s.c_str(); + // Replace '\' with '\\' or '`' with '``' + ret = Strings::replace_all(std::move(ret), {escape_char}, {escape_char, escape_char}); + // Replace '"' with '\"' or '`"' + ret = Strings::replace_all(std::move(ret), {char_to_escape}, {escape_char, char_to_escape}); + return ret; + } + + std::string::const_iterator case_insensitive_ascii_find(const std::string& s, const std::string& pattern) + { + const std::string pattern_as_lower_case(ascii_to_lowercase(pattern)); + return search(s.begin(), + s.end(), + pattern_as_lower_case.begin(), + pattern_as_lower_case.end(), + [](const char a, const char b) { return details::tolower_char(a) == b; }); + } + + bool case_insensitive_ascii_contains(const std::string& s, const std::string& pattern) + { + return case_insensitive_ascii_find(s, pattern) != s.end(); + } + + bool case_insensitive_ascii_equals(const CStringView left, const CStringView right) + { +#if defined(_WIN32) + return _stricmp(left.c_str(), right.c_str()) == 0; +#else + return strcasecmp(left.c_str(), right.c_str()) == 0; +#endif + } + + std::string ascii_to_lowercase(std::string s) + { + std::transform(s.begin(), s.end(), s.begin(), &details::tolower_char); + return s; + } + + std::string ascii_to_uppercase(std::string s) + { + std::transform(s.begin(), s.end(), s.begin(), &details::toupper_char); + return s; + } + + bool case_insensitive_ascii_starts_with(const std::string& s, const std::string& pattern) + { +#if defined(_WIN32) + return _strnicmp(s.c_str(), pattern.c_str(), pattern.size()) == 0; +#else + return strncasecmp(s.c_str(), pattern.c_str(), pattern.size()) == 0; +#endif + } + + bool ends_with(const std::string& s, StringLiteral pattern) + { + if (s.size() < pattern.size()) return false; + return std::equal(s.end() - pattern.size(), s.end(), pattern.c_str(), pattern.c_str() + pattern.size()); + } + + std::string replace_all(std::string&& s, const std::string& search, const std::string& rep) + { + size_t pos = 0; + while ((pos = s.find(search, pos)) != std::string::npos) + { + s.replace(pos, search.size(), rep); + pos += rep.size(); + } + return std::move(s); + } + + std::string trim(std::string&& s) + { + s.erase(std::find_if_not(s.rbegin(), s.rend(), details::is_space).base(), s.end()); + s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), details::is_space)); + return std::move(s); + } + + void trim_all_and_remove_whitespace_strings(std::vector<std::string>* strings) + { + for (std::string& s : *strings) + { + s = trim(std::move(s)); + } + + Util::erase_remove_if(*strings, [](const std::string& s) { return s.empty(); }); + } + + std::vector<std::string> split(const std::string& s, const std::string& delimiter) + { + std::vector<std::string> output; + + if (delimiter.empty()) + { + output.push_back(s); + return output; + } + + const size_t delimiter_length = delimiter.length(); + size_t i = 0; + for (size_t pos = s.find(delimiter); pos != std::string::npos; pos = s.find(delimiter, pos)) + { + output.push_back(s.substr(i, pos - i)); + pos += delimiter_length; + i = pos; + } + + // Add the rest of the string after the last delimiter, unless there is nothing after it + if (i != s.length()) + { + output.push_back(s.substr(i, s.length())); + } + + return output; + } +} diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp new file mode 100644 index 000000000..1aa12d0a4 --- /dev/null +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -0,0 +1,504 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/system.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/metrics.h> + +#include <ctime> + +#if defined(__APPLE__) +#include <mach-o/dyld.h> +#endif + +#if defined(__FreeBSD__) +#include <sys/sysctl.h> +#endif + +#pragma comment(lib, "Advapi32") + +namespace vcpkg::System +{ + tm get_current_date_time() + { + using std::chrono::system_clock; + std::time_t now_time = system_clock::to_time_t(system_clock::now()); + tm parts{}; +#if defined(_WIN32) + localtime_s(&parts, &now_time); +#else + parts = *localtime(&now_time); +#endif + return parts; + } + + fs::path get_exe_path_of_current_process() + { +#if defined(_WIN32) + wchar_t buf[_MAX_PATH]; + const int bytes = GetModuleFileNameW(nullptr, buf, _MAX_PATH); + if (bytes == 0) std::abort(); + return fs::path(buf, buf + bytes); +#elif defined(__APPLE__) + static constexpr const uint32_t buff_size = 1024 * 32; + uint32_t size = buff_size; + char buf[buff_size] = {}; + bool result = _NSGetExecutablePath(buf, &size); + Checks::check_exit(VCPKG_LINE_INFO, result != -1, "Could not determine current executable path."); + std::unique_ptr<char> canonicalPath(realpath(buf, NULL)); + Checks::check_exit(VCPKG_LINE_INFO, result != -1, "Could not determine current executable path."); + return fs::path(std::string(canonicalPath.get())); +#elif defined(__FreeBSD__) + int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; + char exePath[2048]; + size_t len = sizeof(exePath); + auto rcode = sysctl(mib, 4, exePath, &len, NULL, 0); + Checks::check_exit(VCPKG_LINE_INFO, rcode == 0, "Could not determine current executable path."); + Checks::check_exit(VCPKG_LINE_INFO, len > 0, "Could not determine current executable path."); + return fs::path(exePath, exePath + len - 1); +#else /* LINUX */ + std::array<char, 1024 * 4> buf; + auto written = readlink("/proc/self/exe", buf.data(), buf.size()); + Checks::check_exit(VCPKG_LINE_INFO, written != -1, "Could not determine current executable path."); + return fs::path(buf.data(), buf.data() + written); +#endif + } + + Optional<CPUArchitecture> to_cpu_architecture(const CStringView& arch) + { + if (Strings::case_insensitive_ascii_equals(arch, "x86")) return CPUArchitecture::X86; + if (Strings::case_insensitive_ascii_equals(arch, "x64")) return CPUArchitecture::X64; + if (Strings::case_insensitive_ascii_equals(arch, "amd64")) return CPUArchitecture::X64; + if (Strings::case_insensitive_ascii_equals(arch, "arm")) return CPUArchitecture::ARM; + if (Strings::case_insensitive_ascii_equals(arch, "arm64")) return CPUArchitecture::ARM64; + return nullopt; + } + + CPUArchitecture get_host_processor() + { +#if defined(_WIN32) + auto w6432 = get_environment_variable("PROCESSOR_ARCHITEW6432"); + if (const auto p = w6432.get()) return to_cpu_architecture(*p).value_or_exit(VCPKG_LINE_INFO); + + const auto procarch = get_environment_variable("PROCESSOR_ARCHITECTURE").value_or_exit(VCPKG_LINE_INFO); + return to_cpu_architecture(procarch).value_or_exit(VCPKG_LINE_INFO); +#else +#if defined(__x86_64__) || defined(_M_X64) + return CPUArchitecture::X64; +#elif defined(__x86__) || defined(_M_X86) + return CPUArchitecture::X86; +#else +#error "Unknown host architecture" +#endif +#endif + } + + std::vector<CPUArchitecture> get_supported_host_architectures() + { + std::vector<CPUArchitecture> supported_architectures; + supported_architectures.push_back(get_host_processor()); + + // AMD64 machines support to run x86 applications + if (supported_architectures.back() == CPUArchitecture::X64) + { + supported_architectures.push_back(CPUArchitecture::X86); + } + + return supported_architectures; + } + + CMakeVariable::CMakeVariable(const CStringView varname, const char* varvalue) + : s(Strings::format(R"("-D%s=%s")", varname, varvalue)) + { + } + CMakeVariable::CMakeVariable(const CStringView varname, const std::string& varvalue) + : CMakeVariable(varname, varvalue.c_str()) + { + } + CMakeVariable::CMakeVariable(const CStringView varname, const fs::path& path) + : CMakeVariable(varname, path.generic_u8string()) + { + } + + std::string make_cmake_cmd(const fs::path& cmake_exe, + const fs::path& cmake_script, + const std::vector<CMakeVariable>& pass_variables) + { + const std::string cmd_cmake_pass_variables = Strings::join(" ", pass_variables, [](auto&& v) { return v.s; }); + return Strings::format( + R"("%s" %s -P "%s")", cmake_exe.u8string(), cmd_cmake_pass_variables, cmake_script.generic_u8string()); + } + + int cmd_execute_clean(const CStringView cmd_line, const std::unordered_map<std::string, std::string>& extra_env) + { + auto timer = Chrono::ElapsedTimer::create_started(); +#if defined(_WIN32) + static const std::string SYSTEM_ROOT = get_environment_variable("SystemRoot").value_or_exit(VCPKG_LINE_INFO); + static const std::string SYSTEM_32 = SYSTEM_ROOT + R"(\system32)"; + std::string new_path = Strings::format( + R"(Path=%s;%s;%s\Wbem;%s\WindowsPowerShell\v1.0\)", SYSTEM_32, SYSTEM_ROOT, SYSTEM_32, SYSTEM_32); + + std::vector<std::wstring> env_wstrings = { + L"ALLUSERSPROFILE", + L"APPDATA", + L"CommonProgramFiles", + L"CommonProgramFiles(x86)", + L"CommonProgramW6432", + L"COMPUTERNAME", + L"ComSpec", + L"HOMEDRIVE", + L"HOMEPATH", + L"LOCALAPPDATA", + L"LOGONSERVER", + L"NUMBER_OF_PROCESSORS", + L"OS", + L"PATHEXT", + L"PROCESSOR_ARCHITECTURE", + L"PROCESSOR_ARCHITEW6432", + L"PROCESSOR_IDENTIFIER", + L"PROCESSOR_LEVEL", + L"PROCESSOR_REVISION", + L"ProgramData", + L"ProgramFiles", + L"ProgramFiles(x86)", + L"ProgramW6432", + L"PROMPT", + L"PSModulePath", + L"PUBLIC", + L"SystemDrive", + L"SystemRoot", + L"TEMP", + L"TMP", + L"USERDNSDOMAIN", + L"USERDOMAIN", + L"USERDOMAIN_ROAMINGPROFILE", + L"USERNAME", + L"USERPROFILE", + L"windir", + // Enables proxy information to be passed to Curl, the underlying download library in cmake.exe + L"http_proxy", + L"https_proxy", + // Enables find_package(CUDA) in CMake + L"CUDA_PATH", + // Environmental variable generated automatically by CUDA after installation + L"NVCUDASAMPLES_ROOT", + }; + + // Flush stdout before launching external process + fflush(nullptr); + + std::wstring env_cstr; + + for (auto&& env_wstring : env_wstrings) + { + const Optional<std::string> value = System::get_environment_variable(Strings::to_utf8(env_wstring.c_str())); + const auto v = value.get(); + if (!v || v->empty()) continue; + + env_cstr.append(env_wstring); + env_cstr.push_back(L'='); + env_cstr.append(Strings::to_utf16(*v)); + env_cstr.push_back(L'\0'); + } + + if (extra_env.find("PATH") != extra_env.end()) + new_path += Strings::format(";%s", extra_env.find("PATH")->second); + env_cstr.append(Strings::to_utf16(new_path)); + env_cstr.push_back(L'\0'); + env_cstr.append(L"VSLANG=1033"); + env_cstr.push_back(L'\0'); + + for (const auto& item : extra_env) + { + if (item.first == "PATH") continue; + env_cstr.append(Strings::to_utf16(item.first)); + env_cstr.push_back(L'='); + env_cstr.append(Strings::to_utf16(item.second)); + env_cstr.push_back(L'\0'); + } + + STARTUPINFOW startup_info; + memset(&startup_info, 0, sizeof(STARTUPINFOW)); + startup_info.cb = sizeof(STARTUPINFOW); + + PROCESS_INFORMATION process_info; + memset(&process_info, 0, sizeof(PROCESS_INFORMATION)); + + // Basically we are wrapping it in quotes + const std::string actual_cmd_line = Strings::format(R"###(cmd.exe /c "%s")###", cmd_line); + Debug::println("CreateProcessW(%s)", actual_cmd_line); + bool succeeded = TRUE == CreateProcessW(nullptr, + Strings::to_utf16(actual_cmd_line).data(), + nullptr, + nullptr, + FALSE, + IDLE_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, + env_cstr.data(), + nullptr, + &startup_info, + &process_info); + + Checks::check_exit(VCPKG_LINE_INFO, succeeded, "Process creation failed with error code: %lu", GetLastError()); + + CloseHandle(process_info.hThread); + + const DWORD result = WaitForSingleObject(process_info.hProcess, INFINITE); + Checks::check_exit(VCPKG_LINE_INFO, result != WAIT_FAILED, "WaitForSingleObject failed"); + + DWORD exit_code = 0; + GetExitCodeProcess(process_info.hProcess, &exit_code); + + Debug::println("CreateProcessW() returned %lu after %d us", exit_code, static_cast<int>(timer.microseconds())); + return static_cast<int>(exit_code); +#else + Debug::println("system(%s)", cmd_line.c_str()); + fflush(nullptr); + int rc = system(cmd_line.c_str()); + Debug::println("system() returned %d", rc); + return rc; +#endif + } + + int cmd_execute(const CStringView cmd_line) + { + // Flush stdout before launching external process + fflush(nullptr); + + // Basically we are wrapping it in quotes +#if defined(_WIN32) + const std::string& actual_cmd_line = Strings::format(R"###("%s")###", cmd_line); + Debug::println("_wsystem(%s)", actual_cmd_line); + const int exit_code = _wsystem(Strings::to_utf16(actual_cmd_line).c_str()); + Debug::println("_wsystem() returned %d", exit_code); +#else + Debug::println("_system(%s)", cmd_line); + const int exit_code = system(cmd_line.c_str()); + Debug::println("_system() returned %d", exit_code); +#endif + return exit_code; + } + + ExitCodeAndOutput cmd_execute_and_capture_output(const CStringView cmd_line) + { + // Flush stdout before launching external process + fflush(stdout); + + auto timer = Chrono::ElapsedTimer::create_started(); + +#if defined(_WIN32) + const auto actual_cmd_line = Strings::format(R"###("%s 2>&1")###", cmd_line); + + Debug::println("_wpopen(%s)", actual_cmd_line); + std::wstring output; + wchar_t buf[1024]; + const auto pipe = _wpopen(Strings::to_utf16(actual_cmd_line).c_str(), L"r"); + if (pipe == nullptr) + { + return {1, Strings::to_utf8(output.c_str())}; + } + while (fgetws(buf, 1024, pipe)) + { + output.append(buf); + } + if (!feof(pipe)) + { + return {1, Strings::to_utf8(output.c_str())}; + } + + const auto ec = _pclose(pipe); + + // On Win7, output from powershell calls contain a utf-8 byte order mark in the utf-16 stream, so we strip it + // out if it is present. 0xEF,0xBB,0xBF is the UTF-8 byte-order mark + const wchar_t* a = output.c_str(); + while (output.size() >= 3 && a[0] == 0xEF && a[1] == 0xBB && a[2] == 0xBF) + { + output.erase(0, 3); + } + + Debug::println("_pclose() returned %d after %8d us", ec, static_cast<int>(timer.microseconds())); + + return {ec, Strings::to_utf8(output.c_str())}; +#else + const auto actual_cmd_line = Strings::format(R"###(%s 2>&1)###", cmd_line); + + Debug::println("popen(%s)", actual_cmd_line); + std::string output; + char buf[1024]; + const auto pipe = popen(actual_cmd_line.c_str(), "r"); + if (pipe == nullptr) + { + return {1, output}; + } + while (fgets(buf, 1024, pipe)) + { + output.append(buf); + } + if (!feof(pipe)) + { + return {1, output}; + } + + const auto ec = pclose(pipe); + + Debug::println("_pclose() returned %d after %8d us", ec, (int)timer.microseconds()); + + return {ec, output}; +#endif + } + + void println() { putchar('\n'); } + + void print(const CStringView message) { fputs(message.c_str(), stdout); } + + void println(const CStringView message) + { + print(message); + println(); + } + + void print(const Color c, const CStringView message) + { +#if defined(_WIN32) + const HANDLE console_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + CONSOLE_SCREEN_BUFFER_INFO console_screen_buffer_info{}; + GetConsoleScreenBufferInfo(console_handle, &console_screen_buffer_info); + const auto original_color = console_screen_buffer_info.wAttributes; + + SetConsoleTextAttribute(console_handle, static_cast<WORD>(c) | (original_color & 0xF0)); + print(message); + SetConsoleTextAttribute(console_handle, original_color); +#else + print(message); +#endif + } + + void println(const Color c, const CStringView message) + { + print(c, message); + println(); + } + + Optional<std::string> get_environment_variable(const CStringView varname) noexcept + { +#if defined(_WIN32) + const auto w_varname = Strings::to_utf16(varname); + const auto sz = GetEnvironmentVariableW(w_varname.c_str(), nullptr, 0); + if (sz == 0) return nullopt; + + std::wstring ret(sz, L'\0'); + + Checks::check_exit(VCPKG_LINE_INFO, MAXDWORD >= ret.size()); + const auto sz2 = GetEnvironmentVariableW(w_varname.c_str(), ret.data(), static_cast<DWORD>(ret.size())); + Checks::check_exit(VCPKG_LINE_INFO, sz2 + 1 == sz); + ret.pop_back(); + return Strings::to_utf8(ret.c_str()); +#else + auto v = getenv(varname.c_str()); + if (!v) return nullopt; + return std::string(v); +#endif + } + +#if defined(_WIN32) + static bool is_string_keytype(const DWORD hkey_type) + { + return hkey_type == REG_SZ || hkey_type == REG_MULTI_SZ || hkey_type == REG_EXPAND_SZ; + } + + Optional<std::string> get_registry_string(void* base_hkey, const CStringView sub_key, const CStringView valuename) + { + HKEY k = nullptr; + const LSTATUS ec = + RegOpenKeyExW(reinterpret_cast<HKEY>(base_hkey), Strings::to_utf16(sub_key).c_str(), NULL, KEY_READ, &k); + if (ec != ERROR_SUCCESS) return nullopt; + + DWORD dw_buffer_size = 0; + DWORD dw_type = 0; + auto rc = + RegQueryValueExW(k, Strings::to_utf16(valuename).c_str(), nullptr, &dw_type, nullptr, &dw_buffer_size); + if (rc != ERROR_SUCCESS || !is_string_keytype(dw_type) || dw_buffer_size == 0 || + dw_buffer_size % sizeof(wchar_t) != 0) + return nullopt; + std::wstring ret; + ret.resize(dw_buffer_size / sizeof(wchar_t)); + + rc = RegQueryValueExW(k, + Strings::to_utf16(valuename).c_str(), + nullptr, + &dw_type, + reinterpret_cast<LPBYTE>(ret.data()), + &dw_buffer_size); + if (rc != ERROR_SUCCESS || !is_string_keytype(dw_type) || dw_buffer_size != sizeof(wchar_t) * ret.size()) + return nullopt; + + ret.pop_back(); // remove extra trailing null byte + return Strings::to_utf8(ret.c_str()); + } +#else + Optional<std::string> get_registry_string(void* base_hkey, const CStringView sub_key, const CStringView valuename) + { + return nullopt; + } +#endif + + static const Optional<fs::path>& get_program_files() + { + static const auto PATH = []() -> Optional<fs::path> { + auto value = System::get_environment_variable("PROGRAMFILES"); + if (auto v = value.get()) + { + return *v; + } + + return nullopt; + }(); + + return PATH; + } + + const Optional<fs::path>& get_program_files_32_bit() + { + static const auto PATH = []() -> Optional<fs::path> { + auto value = System::get_environment_variable("ProgramFiles(x86)"); + if (auto v = value.get()) + { + return *v; + } + return get_program_files(); + }(); + return PATH; + } + + const Optional<fs::path>& get_program_files_platform_bitness() + { + static const auto PATH = []() -> Optional<fs::path> { + auto value = System::get_environment_variable("ProgramW6432"); + if (auto v = value.get()) + { + return *v; + } + return get_program_files(); + }(); + return PATH; + } +} + +namespace vcpkg::Debug +{ + void println(const CStringView message) + { + if (GlobalState::debugging) + { + System::println("[DEBUG] %s", message); + } + } + + void println(const System::Color c, const CStringView message) + { + if (GlobalState::debugging) + { + System::println(c, "[DEBUG] %s", message); + } + } +} diff --git a/toolsrc/src/vcpkg/binaryparagraph.cpp b/toolsrc/src/vcpkg/binaryparagraph.cpp new file mode 100644 index 000000000..73ca23df1 --- /dev/null +++ b/toolsrc/src/vcpkg/binaryparagraph.cpp @@ -0,0 +1,123 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/binaryparagraph.h> +#include <vcpkg/parse.h> + +namespace vcpkg +{ + namespace Fields + { + static const std::string PACKAGE = "Package"; + static const std::string VERSION = "Version"; + static const std::string ARCHITECTURE = "Architecture"; + static const std::string MULTI_ARCH = "Multi-Arch"; + } + + namespace Fields + { + static const std::string ABI = "Abi"; + static const std::string FEATURE = "Feature"; + static const std::string DESCRIPTION = "Description"; + static const std::string MAINTAINER = "Maintainer"; + static const std::string DEPENDS = "Depends"; + static const std::string DEFAULTFEATURES = "Default-Features"; + } + + BinaryParagraph::BinaryParagraph() = default; + + BinaryParagraph::BinaryParagraph(std::unordered_map<std::string, std::string> fields) + { + using namespace vcpkg::Parse; + + ParagraphParser parser(std::move(fields)); + + { + std::string name; + parser.required_field(Fields::PACKAGE, name); + std::string architecture; + parser.required_field(Fields::ARCHITECTURE, architecture); + this->spec = PackageSpec::from_name_and_triplet(name, Triplet::from_canonical_name(architecture)) + .value_or_exit(VCPKG_LINE_INFO); + } + + // one or the other + this->version = parser.optional_field(Fields::VERSION); + this->feature = parser.optional_field(Fields::FEATURE); + + this->description = parser.optional_field(Fields::DESCRIPTION); + this->maintainer = parser.optional_field(Fields::MAINTAINER); + + this->abi = parser.optional_field(Fields::ABI); + + std::string multi_arch; + parser.required_field(Fields::MULTI_ARCH, multi_arch); + + this->depends = parse_comma_list(parser.optional_field(Fields::DEPENDS)); + if (this->feature.empty()) + { + this->default_features = parse_comma_list(parser.optional_field(Fields::DEFAULTFEATURES)); + } + + if (const auto err = parser.error_info(this->spec.to_string())) + { + System::println( + System::Color::error, "Error: while parsing the Binary Paragraph for %s", this->spec.to_string()); + print_error_message(err); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + // prefer failing above when possible because it gives better information + Checks::check_exit(VCPKG_LINE_INFO, multi_arch == "same", "Multi-Arch must be 'same' but was %s", multi_arch); + } + + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const Triplet& triplet, const std::string& abi_tag) + : version(spgh.version), description(spgh.description), maintainer(spgh.maintainer), abi(abi_tag) + { + this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); + this->depends = filter_dependencies(spgh.depends, triplet); + } + + BinaryParagraph::BinaryParagraph(const SourceParagraph& spgh, const FeatureParagraph& fpgh, const Triplet& triplet) + : version(), description(fpgh.description), maintainer(), feature(fpgh.name) + { + this->spec = PackageSpec::from_name_and_triplet(spgh.name, triplet).value_or_exit(VCPKG_LINE_INFO); + this->depends = filter_dependencies(fpgh.depends, triplet); + } + + std::string BinaryParagraph::displayname() const + { + if (this->feature.empty() || this->feature == "core") + return Strings::format("%s:%s", this->spec.name(), this->spec.triplet()); + return Strings::format("%s[%s]:%s", this->spec.name(), this->feature, this->spec.triplet()); + } + + std::string BinaryParagraph::dir() const { return this->spec.dir(); } + + std::string BinaryParagraph::fullstem() const + { + return Strings::format("%s_%s_%s", this->spec.name(), this->version, this->spec.triplet()); + } + + void serialize(const BinaryParagraph& pgh, std::string& out_str) + { + out_str.append("Package: ").append(pgh.spec.name()).push_back('\n'); + if (!pgh.version.empty()) + out_str.append("Version: ").append(pgh.version).push_back('\n'); + else if (!pgh.feature.empty()) + out_str.append("Feature: ").append(pgh.feature).push_back('\n'); + if (!pgh.depends.empty()) + { + out_str.append("Depends: "); + out_str.append(Strings::join(", ", pgh.depends)); + out_str.push_back('\n'); + } + + out_str.append("Architecture: ").append(pgh.spec.triplet().to_string()).push_back('\n'); + out_str.append("Multi-Arch: same\n"); + + if (!pgh.maintainer.empty()) out_str.append("Maintainer: ").append(pgh.maintainer).push_back('\n'); + if (!pgh.abi.empty()) out_str.append("Abi: ").append(pgh.abi).push_back('\n'); + if (!pgh.description.empty()) out_str.append("Description: ").append(pgh.description).push_back('\n'); + } +} diff --git a/toolsrc/src/vcpkg/build.cpp b/toolsrc/src/vcpkg/build.cpp new file mode 100644 index 000000000..b8ccb15bf --- /dev/null +++ b/toolsrc/src/vcpkg/build.cpp @@ -0,0 +1,892 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/chrono.h> +#include <vcpkg/base/enums.h> +#include <vcpkg/base/optional.h> +#include <vcpkg/base/stringliteral.h> +#include <vcpkg/base/system.h> +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/metrics.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/postbuildlint.h> +#include <vcpkg/statusparagraphs.h> +#include <vcpkg/vcpkglib.h> + +using vcpkg::Build::BuildResult; +using vcpkg::Parse::ParseControlErrorInfo; +using vcpkg::Parse::ParseExpected; + +namespace vcpkg::Build::Command +{ + using Dependencies::InstallPlanAction; + using Dependencies::InstallPlanType; + + static constexpr StringLiteral OPTION_CHECKS_ONLY = "--checks-only"; + + void perform_and_exit_ex(const FullPackageSpec& full_spec, + const fs::path& port_dir, + const ParsedArguments& options, + const VcpkgPaths& paths) + { + const PackageSpec& spec = full_spec.package_spec; + if (Util::Sets::contains(options.switches, OPTION_CHECKS_ONLY)) + { + const auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, spec.triplet()); + const auto build_info = Build::read_build_info(paths.get_filesystem(), paths.build_info_file_path(spec)); + const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); + Checks::check_exit(VCPKG_LINE_INFO, error_count == 0); + Checks::exit_success(VCPKG_LINE_INFO); + } + + const ParseExpected<SourceControlFile> source_control_file = + Paragraphs::try_load_port(paths.get_filesystem(), port_dir); + + if (!source_control_file.has_value()) + { + print_error_message(source_control_file.error()); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + const auto& scf = source_control_file.value_or_exit(VCPKG_LINE_INFO); + Checks::check_exit(VCPKG_LINE_INFO, + spec.name() == scf->core_paragraph->name, + "The Source field inside the CONTROL file does not match the port directory: '%s' != '%s'", + scf->core_paragraph->name, + spec.name()); + + const StatusParagraphs status_db = database_load_check(paths); + const Build::BuildPackageOptions build_package_options{Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN}; + + std::set<std::string> features_as_set(full_spec.features.begin(), full_spec.features.end()); + features_as_set.emplace("core"); + + const Build::BuildPackageConfig build_config{ + *scf, spec.triplet(), fs::path{port_dir}, build_package_options, features_as_set}; + + const auto build_timer = Chrono::ElapsedTimer::create_started(); + const auto result = Build::build_package(paths, build_config, status_db); + System::println("Elapsed time for package %s: %s", spec.to_string(), build_timer.to_string()); + + if (result.code == BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES) + { + System::println(System::Color::error, + "The build command requires all dependencies to be already installed."); + System::println("The following dependencies are missing:"); + System::println(); + for (const auto& p : result.unmet_dependencies) + { + System::println(" %s", p); + } + System::println(); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + Checks::check_exit(VCPKG_LINE_INFO, result.code != BuildResult::EXCLUDED); + + if (result.code != BuildResult::SUCCEEDED) + { + System::println(System::Color::error, Build::create_error_message(result.code, spec)); + System::println(Build::create_user_troubleshooting_message(spec)); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } + + static constexpr std::array<CommandSwitch, 1> BUILD_SWITCHES = {{ + {OPTION_CHECKS_ONLY, "Only run checks, do not rebuild package"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("build zlib:x64-windows"), + 1, + 1, + {BUILD_SWITCHES, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + // Build only takes a single package and all dependencies must already be installed + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + const std::string command_argument = args.command_arguments.at(0); + const FullPackageSpec spec = + Input::check_and_get_full_package_spec(command_argument, default_triplet, COMMAND_STRUCTURE.example_text); + Input::check_triplet(spec.package_spec.triplet(), paths); + if (!spec.features.empty() && !GlobalState::feature_packages) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Feature packages are experimentally available under the --featurepackages flag."); + } + perform_and_exit_ex(spec, paths.port_dir(spec.package_spec), options, paths); + } +} + +namespace vcpkg::Build +{ + static const std::string NAME_EMPTY_PACKAGE = "PolicyEmptyPackage"; + static const std::string NAME_DLLS_WITHOUT_LIBS = "PolicyDLLsWithoutLIBs"; + static const std::string NAME_ONLY_RELEASE_CRT = "PolicyOnlyReleaseCRT"; + static const std::string NAME_EMPTY_INCLUDE_FOLDER = "PolicyEmptyIncludeFolder"; + static const std::string NAME_ALLOW_OBSOLETE_MSVCRT = "PolicyAllowObsoleteMsvcrt"; + + const std::string& to_string(BuildPolicy policy) + { + switch (policy) + { + case BuildPolicy::EMPTY_PACKAGE: return NAME_EMPTY_PACKAGE; + case BuildPolicy::DLLS_WITHOUT_LIBS: return NAME_DLLS_WITHOUT_LIBS; + case BuildPolicy::ONLY_RELEASE_CRT: return NAME_ONLY_RELEASE_CRT; + case BuildPolicy::EMPTY_INCLUDE_FOLDER: return NAME_EMPTY_INCLUDE_FOLDER; + case BuildPolicy::ALLOW_OBSOLETE_MSVCRT: return NAME_ALLOW_OBSOLETE_MSVCRT; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + CStringView to_cmake_variable(BuildPolicy policy) + { + switch (policy) + { + case BuildPolicy::EMPTY_PACKAGE: return "VCPKG_POLICY_EMPTY_PACKAGE"; + case BuildPolicy::DLLS_WITHOUT_LIBS: return "VCPKG_POLICY_DLLS_WITHOUT_LIBS"; + case BuildPolicy::ONLY_RELEASE_CRT: return "VCPKG_POLICY_ONLY_RELEASE_CRT"; + case BuildPolicy::EMPTY_INCLUDE_FOLDER: return "VCPKG_POLICY_EMPTY_INCLUDE_FOLDER"; + case BuildPolicy::ALLOW_OBSOLETE_MSVCRT: return "VCPKG_POLICY_ALLOW_OBSOLETE_MSVCRT"; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + static const std::string NAME_BUILD_IN_DOWNLOAD = "BUILT_IN"; + static const std::string NAME_ARIA2_DOWNLOAD = "ARIA2"; + + const std::string& to_string(DownloadTool tool) + { + switch (tool) + { + case DownloadTool::BUILT_IN: return NAME_BUILD_IN_DOWNLOAD; + case DownloadTool::ARIA2: return NAME_ARIA2_DOWNLOAD; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + Optional<LinkageType> to_linkage_type(const std::string& str) + { + if (str == "dynamic") return LinkageType::DYNAMIC; + if (str == "static") return LinkageType::STATIC; + return nullopt; + } + + namespace BuildInfoRequiredField + { + static const std::string CRT_LINKAGE = "CRTLinkage"; + static const std::string LIBRARY_LINKAGE = "LibraryLinkage"; + } + + CStringView to_vcvarsall_target(const std::string& cmake_system_name) + { + if (cmake_system_name.empty()) return ""; + if (cmake_system_name == "Windows") return ""; + if (cmake_system_name == "WindowsStore") return "store"; + + Checks::exit_with_message(VCPKG_LINE_INFO, "Unsupported vcvarsall target %s", cmake_system_name); + } + + CStringView to_vcvarsall_toolchain(const std::string& target_architecture, const Toolset& toolset) + { + auto maybe_target_arch = System::to_cpu_architecture(target_architecture); + Checks::check_exit( + VCPKG_LINE_INFO, maybe_target_arch.has_value(), "Invalid architecture string: %s", target_architecture); + auto target_arch = maybe_target_arch.value_or_exit(VCPKG_LINE_INFO); + auto host_architectures = System::get_supported_host_architectures(); + + for (auto&& host : host_architectures) + { + const auto it = Util::find_if(toolset.supported_architectures, [&](const ToolsetArchOption& opt) { + return host == opt.host_arch && target_arch == opt.target_arch; + }); + if (it != toolset.supported_architectures.end()) return it->name; + } + + Checks::exit_with_message(VCPKG_LINE_INFO, + "Unsupported toolchain combination. Target was: %s but supported ones were:\n%s", + target_architecture, + Strings::join(",", toolset.supported_architectures, [](const ToolsetArchOption& t) { + return t.name.c_str(); + })); + } + + std::string make_build_env_cmd(const PreBuildInfo& pre_build_info, const Toolset& toolset) + { + if (pre_build_info.external_toolchain_file.has_value()) return ""; + if (!pre_build_info.cmake_system_name.empty() && pre_build_info.cmake_system_name != "WindowsStore") return ""; + + const char* tonull = " >nul"; + if (GlobalState::debugging) + { + tonull = ""; + } + + const auto arch = to_vcvarsall_toolchain(pre_build_info.target_architecture, toolset); + const auto target = to_vcvarsall_target(pre_build_info.cmake_system_name); + + return Strings::format(R"("%s" %s %s %s %s 2>&1)", + toolset.vcvarsall.u8string(), + Strings::join(" ", toolset.vcvarsall_options), + arch, + target, + tonull); + } + + static BinaryParagraph create_binary_feature_control_file(const SourceParagraph& source_paragraph, + const FeatureParagraph& feature_paragraph, + const Triplet& triplet) + { + return BinaryParagraph(source_paragraph, feature_paragraph, triplet); + } + + static std::unique_ptr<BinaryControlFile> create_binary_control_file(const SourceParagraph& source_paragraph, + const Triplet& triplet, + const BuildInfo& build_info, + const std::string& abi_tag) + { + auto bcf = std::make_unique<BinaryControlFile>(); + BinaryParagraph bpgh(source_paragraph, triplet, abi_tag); + if (const auto p_ver = build_info.version.get()) + { + bpgh.version = *p_ver; + } + bcf->core_paragraph = std::move(bpgh); + return bcf; + } + + static void write_binary_control_file(const VcpkgPaths& paths, BinaryControlFile bcf) + { + std::string start = Strings::serialize(bcf.core_paragraph); + for (auto&& feature : bcf.features) + { + start += "\n" + Strings::serialize(feature); + } + const fs::path binary_control_file = paths.packages / bcf.core_paragraph.dir() / "CONTROL"; + paths.get_filesystem().write_contents(binary_control_file, start); + } + + static std::vector<FeatureSpec> compute_required_feature_specs(const BuildPackageConfig& config, + const StatusParagraphs& status_db) + { + const Triplet& triplet = config.triplet; + + const std::vector<std::string> dep_strings = + Util::fmap_flatten(config.feature_list, [&](std::string const& feature) -> std::vector<std::string> { + if (feature == "core") + { + return filter_dependencies(config.scf.core_paragraph->depends, triplet); + } + + auto maybe_feature = config.scf.find_feature(feature); + Checks::check_exit(VCPKG_LINE_INFO, maybe_feature.has_value()); + + return filter_dependencies(maybe_feature.get()->depends, triplet); + }); + + auto dep_fspecs = FeatureSpec::from_strings_and_triplet(dep_strings, triplet); + Util::sort_unique_erase(dep_fspecs); + + // expand defaults + std::vector<FeatureSpec> ret; + for (auto&& fspec : dep_fspecs) + { + if (fspec.feature().empty()) + { + // reference to default features + const auto it = status_db.find_installed(fspec.spec()); + if (it == status_db.end()) + { + // not currently installed, so just leave the default reference so it will fail later + ret.push_back(fspec); + } + else + { + ret.emplace_back(fspec.spec(), "core"); + for (auto&& default_feature : it->get()->package.default_features) + ret.emplace_back(fspec.spec(), default_feature); + } + } + else + { + ret.push_back(fspec); + } + } + Util::sort_unique_erase(ret); + + return ret; + } + + static ExtendedBuildResult do_build_package(const VcpkgPaths& paths, + const PreBuildInfo& pre_build_info, + const PackageSpec& spec, + const std::string& abi_tag, + const BuildPackageConfig& config) + { + auto& fs = paths.get_filesystem(); + const Triplet& triplet = spec.triplet(); + +#if !defined(_WIN32) + // TODO: remove when vcpkg.exe is in charge for acquiring tools. Change introduced in vcpkg v0.0.107. + // bootstrap should have already downloaded ninja, but making sure it is present in case it was deleted. + vcpkg::Util::unused(paths.get_tool_exe(Tools::NINJA)); +#endif + + const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE); + const fs::path& git_exe_path = paths.get_tool_exe(Tools::GIT); + + std::string all_features; + for (auto& feature : config.scf.feature_paragraphs) + { + all_features.append(feature->name + ";"); + } + + const Toolset& toolset = paths.get_toolset(pre_build_info); + const std::string cmd_launch_cmake = System::make_cmake_cmd( + cmake_exe_path, + paths.ports_cmake, + { + {"CMD", "BUILD"}, + {"PORT", config.scf.core_paragraph->name}, + {"CURRENT_PORT_DIR", config.port_dir}, + {"TARGET_TRIPLET", spec.triplet().canonical_name()}, + {"VCPKG_PLATFORM_TOOLSET", toolset.version.c_str()}, + {"VCPKG_USE_HEAD_VERSION", + Util::Enum::to_bool(config.build_package_options.use_head_version) ? "1" : "0"}, + {"_VCPKG_NO_DOWNLOADS", !Util::Enum::to_bool(config.build_package_options.allow_downloads) ? "1" : "0"}, + {"_VCPKG_DOWNLOAD_TOOL", to_string(config.build_package_options.download_tool)}, + {"GIT", git_exe_path}, + {"FEATURES", Strings::join(";", config.feature_list)}, + {"ALL_FEATURES", all_features}, + }); + + auto command = make_build_env_cmd(pre_build_info, toolset); + if (!command.empty()) command.append(" && "); + command.append(cmd_launch_cmake); + + const auto timer = Chrono::ElapsedTimer::create_started(); + + const int return_code = System::cmd_execute_clean(command); + const auto buildtimeus = timer.microseconds(); + const auto spec_string = spec.to_string(); + + { + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->track_buildtime(spec.to_string() + ":[" + Strings::join(",", config.feature_list) + "]", + buildtimeus); + if (return_code != 0) + { + locked_metrics->track_property("error", "build failed"); + locked_metrics->track_property("build_error", spec_string); + return BuildResult::BUILD_FAILED; + } + } + + const BuildInfo build_info = read_build_info(fs, paths.build_info_file_path(spec)); + const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); + + auto bcf = create_binary_control_file(*config.scf.core_paragraph, triplet, build_info, abi_tag); + + if (error_count != 0) + { + return BuildResult::POST_BUILD_CHECKS_FAILED; + } + for (auto&& feature : config.feature_list) + { + for (auto&& f_pgh : config.scf.feature_paragraphs) + { + if (f_pgh->name == feature) + bcf->features.push_back( + create_binary_feature_control_file(*config.scf.core_paragraph, *f_pgh, triplet)); + } + } + + write_binary_control_file(paths, *bcf); + return {BuildResult::SUCCEEDED, std::move(bcf)}; + } + + static ExtendedBuildResult do_build_package_and_clean_buildtrees(const VcpkgPaths& paths, + const PreBuildInfo& pre_build_info, + const PackageSpec& spec, + const std::string& abi_tag, + const BuildPackageConfig& config) + { + auto result = do_build_package(paths, pre_build_info, spec, abi_tag, config); + + if (config.build_package_options.clean_buildtrees == CleanBuildtrees::YES) + { + auto& fs = paths.get_filesystem(); + const fs::path buildtrees_dir = paths.buildtrees / config.scf.core_paragraph->name; + auto buildtree_files = fs.get_files_non_recursive(buildtrees_dir); + for (auto&& file : buildtree_files) + { + if (fs.is_directory(file) && file.filename() != "src") + { + std::error_code ec; + fs.remove_all(file, ec); + } + } + } + + return result; + } + + Optional<AbiTagAndFile> compute_abi_tag(const VcpkgPaths& paths, + const BuildPackageConfig& config, + const PreBuildInfo& pre_build_info, + Span<const AbiEntry> dependency_abis) + { + if (!GlobalState::g_binary_caching) return nullopt; + + auto& fs = paths.get_filesystem(); + const Triplet& triplet = config.triplet; + const std::string& name = config.scf.core_paragraph->name; + + std::vector<AbiEntry> abi_tag_entries; + + abi_tag_entries.insert(abi_tag_entries.end(), dependency_abis.begin(), dependency_abis.end()); + + abi_tag_entries.emplace_back( + AbiEntry{"portfile", Commands::Hash::get_file_hash(fs, config.port_dir / "portfile.cmake", "SHA1")}); + abi_tag_entries.emplace_back( + AbiEntry{"control", Commands::Hash::get_file_hash(fs, config.port_dir / "CONTROL", "SHA1")}); + + abi_tag_entries.emplace_back(AbiEntry{"triplet", pre_build_info.triplet_abi_tag}); + + const std::string features = Strings::join(";", config.feature_list); + abi_tag_entries.emplace_back(AbiEntry{"features", features}); + + if (config.build_package_options.use_head_version == UseHeadVersion::YES) + abi_tag_entries.emplace_back(AbiEntry{"head", ""}); + + Util::sort(abi_tag_entries); + + const std::string full_abi_info = + Strings::join("", abi_tag_entries, [](const AbiEntry& p) { return p.key + " " + p.value + "\n"; }); + + if (GlobalState::debugging) + { + System::println("[DEBUG] <abientries>"); + for (auto&& entry : abi_tag_entries) + { + System::println("[DEBUG] %s|%s", entry.key, entry.value); + } + System::println("[DEBUG] </abientries>"); + } + + auto abi_tag_entries_missing = abi_tag_entries; + Util::stable_keep_if(abi_tag_entries_missing, [](const AbiEntry& p) { return p.value.empty(); }); + + if (abi_tag_entries_missing.empty()) + { + std::error_code ec; + fs.create_directories(paths.buildtrees / name, ec); + const auto abi_file_path = paths.buildtrees / name / (triplet.canonical_name() + ".vcpkg_abi_info.txt"); + fs.write_contents(abi_file_path, full_abi_info); + + return AbiTagAndFile{Commands::Hash::get_file_hash(fs, abi_file_path, "SHA1"), abi_file_path}; + } + + System::println( + "Warning: binary caching disabled because abi keys are missing values:\n%s", + Strings::join("", abi_tag_entries_missing, [](const AbiEntry& e) { return " " + e.key + "\n"; })); + + return nullopt; + } + + static void decompress_archive(const VcpkgPaths& paths, const PackageSpec& spec, const fs::path& archive_path) + { + auto& fs = paths.get_filesystem(); + + auto pkg_path = paths.package_dir(spec); + std::error_code ec; + fs.remove_all(pkg_path, ec); + fs.create_directories(pkg_path, ec); + auto files = fs.get_files_non_recursive(pkg_path); + Checks::check_exit(VCPKG_LINE_INFO, files.empty(), "unable to clear path: %s", pkg_path.u8string()); + +#if defined(_WIN32) + auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP); + + System::cmd_execute_clean(Strings::format( + R"("%s" x "%s" -o"%s" -y >nul)", seven_zip_exe.u8string(), archive_path.u8string(), pkg_path.u8string())); +#else + System::cmd_execute_clean( + Strings::format(R"(unzip -qq "%s" "-d%s")", archive_path.u8string(), pkg_path.u8string())); +#endif + } + + static void compress_archive(const VcpkgPaths& paths, const PackageSpec& spec, const fs::path& tmp_archive_path) + { + auto& fs = paths.get_filesystem(); + + std::error_code ec; + + fs.remove(tmp_archive_path, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !fs.exists(tmp_archive_path), "Could not remove file: %s", tmp_archive_path.u8string()); +#if defined(_WIN32) + auto&& seven_zip_exe = paths.get_tool_exe(Tools::SEVEN_ZIP); + + System::cmd_execute_clean(Strings::format(R"("%s" a "%s" "%s\*" >nul)", + seven_zip_exe.u8string(), + tmp_archive_path.u8string(), + paths.package_dir(spec).u8string())); +#else + System::cmd_execute_clean(Strings::format( + R"(cd '%s' && zip --quiet -r '%s' *)", paths.package_dir(spec).u8string(), tmp_archive_path.u8string())); +#endif + } + + ExtendedBuildResult build_package(const VcpkgPaths& paths, + const BuildPackageConfig& config, + const StatusParagraphs& status_db) + { + auto& fs = paths.get_filesystem(); + const Triplet& triplet = config.triplet; + const std::string& name = config.scf.core_paragraph->name; + + std::vector<FeatureSpec> required_fspecs = compute_required_feature_specs(config, status_db); + + // extract out the actual package ids + auto dep_pspecs = Util::fmap(required_fspecs, [](FeatureSpec const& fspec) { return fspec.spec(); }); + Util::sort_unique_erase(dep_pspecs); + + // Find all features that aren't installed. This mutates required_fspecs. + Util::unstable_keep_if(required_fspecs, [&](FeatureSpec const& fspec) { + return !status_db.is_installed(fspec) && fspec.name() != name; + }); + + if (!required_fspecs.empty()) + { + return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(required_fspecs)}; + } + + const PackageSpec spec = + PackageSpec::from_name_and_triplet(config.scf.core_paragraph->name, triplet).value_or_exit(VCPKG_LINE_INFO); + + std::vector<AbiEntry> dependency_abis; + + // dep_pspecs was not destroyed + for (auto&& pspec : dep_pspecs) + { + if (pspec == spec) continue; + const auto status_it = status_db.find_installed(pspec); + Checks::check_exit(VCPKG_LINE_INFO, status_it != status_db.end()); + dependency_abis.emplace_back( + AbiEntry{status_it->get()->package.spec.name(), status_it->get()->package.abi}); + } + + const auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet); + + auto maybe_abi_tag_and_file = compute_abi_tag(paths, config, pre_build_info, dependency_abis); + + const auto abi_tag_and_file = maybe_abi_tag_and_file.get(); + + if (GlobalState::g_binary_caching && abi_tag_and_file) + { + const fs::path archives_root_dir = paths.root / "archives"; + const std::string archive_name = abi_tag_and_file->tag + ".zip"; + const fs::path archive_subpath = fs::u8path(abi_tag_and_file->tag.substr(0, 2)) / archive_name; + const fs::path archive_path = archives_root_dir / archive_subpath; + const fs::path archive_tombstone_path = archives_root_dir / "fail" / archive_subpath; + + if (fs.exists(archive_path)) + { + System::println("Using cached binary package: %s", archive_path.u8string()); + + decompress_archive(paths, spec, archive_path); + + auto maybe_bcf = Paragraphs::try_load_cached_package(paths, spec); + std::unique_ptr<BinaryControlFile> bcf = + std::make_unique<BinaryControlFile>(std::move(maybe_bcf).value_or_exit(VCPKG_LINE_INFO)); + return {BuildResult::SUCCEEDED, std::move(bcf)}; + } + + if (fs.exists(archive_tombstone_path)) + { + System::println("Found failure tombstone: %s", archive_tombstone_path.u8string()); + return BuildResult::BUILD_FAILED; + } + + System::println("Could not locate cached archive: %s", archive_path.u8string()); + + ExtendedBuildResult result = do_build_package_and_clean_buildtrees( + paths, pre_build_info, spec, maybe_abi_tag_and_file.value_or(AbiTagAndFile{}).tag, config); + + std::error_code ec; + fs.create_directories(paths.package_dir(spec) / "share" / spec.name(), ec); + auto abi_file_in_package = paths.package_dir(spec) / "share" / spec.name() / "vcpkg_abi_info.txt"; + fs.copy_file(abi_tag_and_file->tag_file, abi_file_in_package, fs::stdfs::copy_options::none, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not copy into file: %s", abi_file_in_package.u8string()); + + if (result.code == BuildResult::SUCCEEDED) + { + const auto tmp_archive_path = paths.buildtrees / spec.name() / (spec.triplet().to_string() + ".zip"); + + compress_archive(paths, spec, tmp_archive_path); + + fs.create_directories(archive_path.parent_path(), ec); + fs.rename(tmp_archive_path, archive_path, ec); + if (ec) + System::println( + System::Color::warning, "Failed to store binary cache: %s", archive_path.u8string()); + else + System::println("Stored binary cache: %s", archive_path.u8string()); + } + else if (result.code == BuildResult::BUILD_FAILED || result.code == BuildResult::POST_BUILD_CHECKS_FAILED) + { + // Build failed, so store tombstone archive + fs.create_directories(archive_tombstone_path.parent_path(), ec); + fs.write_contents(archive_tombstone_path, "", ec); + } + + return result; + } + + return do_build_package_and_clean_buildtrees( + paths, pre_build_info, spec, maybe_abi_tag_and_file.value_or(AbiTagAndFile{}).tag, config); + } + + const std::string& to_string(const BuildResult build_result) + { + static const std::string NULLVALUE_STRING = Enums::nullvalue_to_string("vcpkg::Commands::Build::BuildResult"); + static const std::string SUCCEEDED_STRING = "SUCCEEDED"; + static const std::string BUILD_FAILED_STRING = "BUILD_FAILED"; + static const std::string FILE_CONFLICTS_STRING = "FILE_CONFLICTS"; + static const std::string POST_BUILD_CHECKS_FAILED_STRING = "POST_BUILD_CHECKS_FAILED"; + static const std::string CASCADED_DUE_TO_MISSING_DEPENDENCIES_STRING = "CASCADED_DUE_TO_MISSING_DEPENDENCIES"; + static const std::string EXCLUDED_STRING = "EXCLUDED"; + + switch (build_result) + { + case BuildResult::NULLVALUE: return NULLVALUE_STRING; + case BuildResult::SUCCEEDED: return SUCCEEDED_STRING; + case BuildResult::BUILD_FAILED: return BUILD_FAILED_STRING; + case BuildResult::POST_BUILD_CHECKS_FAILED: return POST_BUILD_CHECKS_FAILED_STRING; + case BuildResult::FILE_CONFLICTS: return FILE_CONFLICTS_STRING; + case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: return CASCADED_DUE_TO_MISSING_DEPENDENCIES_STRING; + case BuildResult::EXCLUDED: return EXCLUDED_STRING; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + std::string create_error_message(const BuildResult build_result, const PackageSpec& spec) + { + return Strings::format("Error: Building package %s failed with: %s", spec, Build::to_string(build_result)); + } + + std::string create_user_troubleshooting_message(const PackageSpec& spec) + { + return Strings::format("Please ensure you're using the latest portfiles with `.\\vcpkg update`, then\n" + "submit an issue at https://github.com/Microsoft/vcpkg/issues including:\n" + " Package: %s\n" + " Vcpkg version: %s\n" + "\n" + "Additionally, attach any relevant sections from the log files above.", + spec, + Commands::Version::version()); + } + + static BuildInfo inner_create_buildinfo(std::unordered_map<std::string, std::string> pgh) + { + Parse::ParagraphParser parser(std::move(pgh)); + + BuildInfo build_info; + + { + std::string crt_linkage_as_string; + parser.required_field(BuildInfoRequiredField::CRT_LINKAGE, crt_linkage_as_string); + + auto crtlinkage = to_linkage_type(crt_linkage_as_string); + if (const auto p = crtlinkage.get()) + build_info.crt_linkage = *p; + else + Checks::exit_with_message(VCPKG_LINE_INFO, "Invalid crt linkage type: [%s]", crt_linkage_as_string); + } + + { + std::string library_linkage_as_string; + parser.required_field(BuildInfoRequiredField::LIBRARY_LINKAGE, library_linkage_as_string); + auto liblinkage = to_linkage_type(library_linkage_as_string); + if (const auto p = liblinkage.get()) + build_info.library_linkage = *p; + else + Checks::exit_with_message( + VCPKG_LINE_INFO, "Invalid library linkage type: [%s]", library_linkage_as_string); + } + std::string version = parser.optional_field("Version"); + if (!version.empty()) build_info.version = std::move(version); + + std::map<BuildPolicy, bool> policies; + for (auto policy : G_ALL_POLICIES) + { + const auto setting = parser.optional_field(to_string(policy)); + if (setting.empty()) continue; + if (setting == "enabled") + policies.emplace(policy, true); + else if (setting == "disabled") + policies.emplace(policy, false); + else + Checks::exit_with_message( + VCPKG_LINE_INFO, "Unknown setting for policy '%s': %s", to_string(policy), setting); + } + + if (const auto err = parser.error_info("PostBuildInformation")) + { + print_error_message(err); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + build_info.policies = BuildPolicies(std::move(policies)); + + return build_info; + } + + BuildInfo read_build_info(const Files::Filesystem& fs, const fs::path& filepath) + { + const Expected<std::unordered_map<std::string, std::string>> pghs = + Paragraphs::get_single_paragraph(fs, filepath); + Checks::check_exit(VCPKG_LINE_INFO, pghs.get() != nullptr, "Invalid BUILD_INFO file for package"); + return inner_create_buildinfo(*pghs.get()); + } + + PreBuildInfo PreBuildInfo::from_triplet_file(const VcpkgPaths& paths, const Triplet& triplet) + { + static constexpr CStringView FLAG_GUID = "c35112b6-d1ba-415b-aa5d-81de856ef8eb"; + + const fs::path& cmake_exe_path = paths.get_tool_exe(Tools::CMAKE); + const fs::path ports_cmake_script_path = paths.scripts / "get_triplet_environment.cmake"; + const fs::path triplet_file_path = paths.triplets / (triplet.canonical_name() + ".cmake"); + + const std::string triplet_abi_tag = [&]() { + static std::map<fs::path, std::string> s_hash_cache; + + if (GlobalState::g_binary_caching) + { + auto it_hash = s_hash_cache.find(triplet_file_path); + if (it_hash != s_hash_cache.end()) + { + return it_hash->second; + } + auto hash = Commands::Hash::get_file_hash(paths.get_filesystem(), triplet_file_path, "SHA1"); + s_hash_cache.emplace(triplet_file_path, hash); + return hash; + } + + return std::string(); + }(); + + const auto cmd_launch_cmake = System::make_cmake_cmd(cmake_exe_path, + ports_cmake_script_path, + { + {"CMAKE_TRIPLET_FILE", triplet_file_path}, + }); + const auto ec_data = System::cmd_execute_and_capture_output(cmd_launch_cmake); + Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, ec_data.output); + + const std::vector<std::string> lines = Strings::split(ec_data.output, "\n"); + + PreBuildInfo pre_build_info; + pre_build_info.triplet_abi_tag = triplet_abi_tag; + + const auto e = lines.cend(); + auto cur = std::find(lines.cbegin(), e, FLAG_GUID); + if (cur != e) ++cur; + + for (; cur != e; ++cur) + { + auto&& line = *cur; + + const std::vector<std::string> s = Strings::split(line, "="); + Checks::check_exit(VCPKG_LINE_INFO, + s.size() == 1 || s.size() == 2, + "Expected format is [VARIABLE_NAME=VARIABLE_VALUE], but was [%s]", + line); + + const bool variable_with_no_value = s.size() == 1; + const std::string variable_name = s.at(0); + const std::string variable_value = variable_with_no_value ? "" : s.at(1); + + if (variable_name == "VCPKG_TARGET_ARCHITECTURE") + { + pre_build_info.target_architecture = variable_value; + continue; + } + + if (variable_name == "VCPKG_CMAKE_SYSTEM_NAME") + { + pre_build_info.cmake_system_name = variable_value; + continue; + } + + if (variable_name == "VCPKG_CMAKE_SYSTEM_VERSION") + { + pre_build_info.cmake_system_version = variable_value; + continue; + } + + if (variable_name == "VCPKG_PLATFORM_TOOLSET") + { + pre_build_info.platform_toolset = + variable_value.empty() ? nullopt : Optional<std::string>{variable_value}; + continue; + } + + if (variable_name == "VCPKG_VISUAL_STUDIO_PATH") + { + pre_build_info.visual_studio_path = + variable_value.empty() ? nullopt : Optional<fs::path>{variable_value}; + continue; + } + + if (variable_name == "VCPKG_CHAINLOAD_TOOLCHAIN_FILE") + { + pre_build_info.external_toolchain_file = + variable_value.empty() ? nullopt : Optional<std::string>{variable_value}; + continue; + } + + if (variable_name == "VCPKG_BUILD_TYPE") + { + if (variable_value.empty()) + pre_build_info.build_type = nullopt; + else if (Strings::case_insensitive_ascii_equals(variable_value, "debug")) + pre_build_info.build_type = ConfigurationType::DEBUG; + else if (Strings::case_insensitive_ascii_equals(variable_value, "release")) + pre_build_info.build_type = ConfigurationType::RELEASE; + else + Checks::exit_with_message( + VCPKG_LINE_INFO, "Unknown setting for VCPKG_BUILD_TYPE: %s", variable_value); + continue; + } + + Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown variable name %s", line); + } + + return pre_build_info; + } + ExtendedBuildResult::ExtendedBuildResult(BuildResult code) : code(code) {} + ExtendedBuildResult::ExtendedBuildResult(BuildResult code, std::unique_ptr<BinaryControlFile>&& bcf) + : code(code), binary_control_file(std::move(bcf)) + { + } + ExtendedBuildResult::ExtendedBuildResult(BuildResult code, std::vector<FeatureSpec>&& unmet_deps) + : code(code), unmet_dependencies(std::move(unmet_deps)) + { + } +} diff --git a/toolsrc/src/vcpkg/commands.autocomplete.cpp b/toolsrc/src/vcpkg/commands.autocomplete.cpp new file mode 100644 index 000000000..564404421 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.autocomplete.cpp @@ -0,0 +1,170 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/install.h> +#include <vcpkg/metrics.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/remove.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Commands::Autocomplete +{ + [[noreturn]] static void output_sorted_results_and_exit(const LineInfo& line_info, + std::vector<std::string>&& results) + { + const SortedVector<std::string> sorted_results(results); + System::println(Strings::join("\n", sorted_results)); + + Checks::exit_success(line_info); + } + + std::vector<std::string> combine_port_with_triplets(const std::string& port, + const std::vector<std::string>& triplets) + { + return Util::fmap(triplets, + [&](const std::string& triplet) { return Strings::format("%s:%s", port, triplet); }); + } + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Metrics::g_metrics.lock()->set_send_metrics(false); + const std::string to_autocomplete = Strings::join(" ", args.command_arguments); + const std::vector<std::string> tokens = Strings::split(to_autocomplete, " "); + + std::smatch match; + + // Handles vcpkg <command> + if (std::regex_match(to_autocomplete, match, std::regex{R"###(^(\S*)$)###"})) + { + const std::string requested_command = match[1].str(); + + // First try public commands + std::vector<std::string> public_commands = { + "install", + "search", + "remove", + "list", + "update", + "hash", + "help", + "integrate", + "export", + "edit", + "create", + "owns", + "cache", + "version", + "contact", + }; + + Util::unstable_keep_if(public_commands, [&](const std::string& s) { + return Strings::case_insensitive_ascii_starts_with(s, requested_command); + }); + + if (!public_commands.empty()) + { + output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(public_commands)); + } + + // If no public commands match, try private commands + std::vector<std::string> private_commands = { + "build", + "buildexternal", + "ci", + "depend-info", + "env", + "import", + "portsdiff", + }; + + Util::unstable_keep_if(private_commands, [&](const std::string& s) { + return Strings::case_insensitive_ascii_starts_with(s, requested_command); + }); + + output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(private_commands)); + } + + // Handles vcpkg install package:<triplet> + if (std::regex_match(to_autocomplete, match, std::regex{R"###(^install(.*|)\s([^:]+):(\S*)$)###"})) + { + const auto port_name = match[2].str(); + const auto triplet_prefix = match[3].str(); + + auto maybe_port = Paragraphs::try_load_port(paths.get_filesystem(), paths.port_dir(port_name)); + if (maybe_port.error()) + { + Checks::exit_success(VCPKG_LINE_INFO); + } + + std::vector<std::string> triplets = paths.get_available_triplets(); + Util::unstable_keep_if(triplets, [&](const std::string& s) { + return Strings::case_insensitive_ascii_starts_with(s, triplet_prefix); + }); + + auto result = combine_port_with_triplets(port_name, triplets); + + output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(result)); + } + + struct CommandEntry + { + constexpr CommandEntry(const CStringView& name, const CStringView& regex, const CommandStructure& structure) + : name(name), regex(regex), structure(structure) + { + } + + CStringView name; + CStringView regex; + const CommandStructure& structure; + }; + + static constexpr CommandEntry COMMANDS[] = { + CommandEntry{"install", R"###(^install\s(.*\s|)(\S*)$)###", Install::COMMAND_STRUCTURE}, + CommandEntry{"edit", R"###(^edit\s(.*\s|)(\S*)$)###", Edit::COMMAND_STRUCTURE}, + CommandEntry{"remove", R"###(^remove\s(.*\s|)(\S*)$)###", Remove::COMMAND_STRUCTURE}, + CommandEntry{"integrate", R"###(^integrate(\s+)(\S*)$)###", Integrate::COMMAND_STRUCTURE}, + }; + + for (auto&& command : COMMANDS) + { + if (std::regex_match(to_autocomplete, match, std::regex{command.regex.c_str()})) + { + const auto prefix = match[2].str(); + std::vector<std::string> results; + + const bool is_option = Strings::case_insensitive_ascii_starts_with(prefix, "-"); + if (is_option) + { + results = Util::fmap(command.structure.options.switches, + [](const CommandSwitch& s) -> std::string { return s.name; }); + + auto settings = Util::fmap(command.structure.options.settings, [](auto&& s) { return s.name; }); + results.insert(results.end(), settings.begin(), settings.end()); + } + else + { + if (command.structure.valid_arguments != nullptr) + { + results = command.structure.valid_arguments(paths); + } + } + + Util::unstable_keep_if(results, [&](const std::string& s) { + return Strings::case_insensitive_ascii_starts_with(s, prefix); + }); + + if (command.name == "install" && results.size() == 1 && !is_option) + { + const auto port_at_each_triplet = + combine_port_with_triplets(results[0], paths.get_available_triplets()); + Util::Vectors::concatenate(&results, port_at_each_triplet); + } + + output_sorted_results_and_exit(VCPKG_LINE_INFO, std::move(results)); + } + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.buildexternal.cpp b/toolsrc/src/vcpkg/commands.buildexternal.cpp new file mode 100644 index 000000000..82d03db48 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.buildexternal.cpp @@ -0,0 +1,29 @@ +#include "pch.h" + +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> + +namespace vcpkg::Commands::BuildExternal +{ + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string(R"(build_external zlib2 C:\path\to\dir\with\controlfile\)"), + 2, + 2, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + const FullPackageSpec spec = Input::check_and_get_full_package_spec( + args.command_arguments.at(0), default_triplet, COMMAND_STRUCTURE.example_text); + Input::check_triplet(spec.package_spec.triplet(), paths); + + const fs::path port_dir = args.command_arguments.at(1); + Build::Command::perform_and_exit_ex(spec, port_dir, options, paths); + } +} diff --git a/toolsrc/src/commands_cache.cpp b/toolsrc/src/vcpkg/commands.cache.cpp index 75de78461..464f4f9ee 100644 --- a/toolsrc/src/commands_cache.cpp +++ b/toolsrc/src/vcpkg/commands.cache.cpp @@ -1,10 +1,11 @@ #include "pch.h" -#include "BinaryParagraph.h" -#include "Paragraphs.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Files.h" -#include "vcpkg_System.h" +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/binaryparagraph.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> namespace vcpkg::Commands::Cache { @@ -15,7 +16,7 @@ namespace vcpkg::Commands::Cache { const Expected<std::unordered_map<std::string, std::string>> pghs = Paragraphs::get_single_paragraph(paths.get_filesystem(), path / "CONTROL"); - if (auto p = pghs.get()) + if (const auto p = pghs.get()) { const BinaryParagraph binary_paragraph = BinaryParagraph(*p); output.push_back(binary_paragraph); @@ -25,13 +26,19 @@ namespace vcpkg::Commands::Cache return output; } + const CommandStructure COMMAND_STRUCTURE = { + Strings::format( + "The argument should be a substring to search for, or no argument to display all cached libraries.\n%s", + Help::create_example_string("cache png")), + 0, + 1, + {}, + nullptr, + }; + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const std::string example = Strings::format( - "The argument should be a substring to search for, or no argument to display all cached libraries.\n%s", - Commands::Help::create_example_string("cache png")); - args.check_max_arg_count(1, example); - args.check_and_get_optional_command_arguments({}); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const std::vector<BinaryParagraph> binary_paragraphs = read_all_binary_paragraphs(paths); if (binary_paragraphs.empty()) @@ -40,7 +47,7 @@ namespace vcpkg::Commands::Cache Checks::exit_success(VCPKG_LINE_INFO); } - if (args.command_arguments.size() == 0) + if (args.command_arguments.empty()) { for (const BinaryParagraph& binary_paragraph : binary_paragraphs) { @@ -54,7 +61,7 @@ namespace vcpkg::Commands::Cache for (const BinaryParagraph& binary_paragraph : binary_paragraphs) { const std::string displayname = binary_paragraph.displayname(); - if (Strings::case_insensitive_ascii_find(displayname, args.command_arguments[0]) == displayname.end()) + if (!Strings::case_insensitive_ascii_contains(displayname, args.command_arguments[0])) { continue; } diff --git a/toolsrc/src/vcpkg/commands.ci.cpp b/toolsrc/src/vcpkg/commands.ci.cpp new file mode 100644 index 000000000..e2b93dc7e --- /dev/null +++ b/toolsrc/src/vcpkg/commands.ci.cpp @@ -0,0 +1,264 @@ +#include "pch.h" + +#include <vcpkg/base/cache.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/stringliteral.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/install.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Commands::CI +{ + using Build::BuildResult; + using Dependencies::InstallPlanAction; + using Dependencies::InstallPlanType; + + struct TripletAndSummary + { + Triplet triplet; + Install::InstallSummary summary; + }; + + static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; + static constexpr StringLiteral OPTION_EXCLUDE = "--exclude"; + static constexpr StringLiteral OPTION_XUNIT = "--x-xunit"; + + static constexpr std::array<CommandSetting, 2> CI_SETTINGS = {{ + {OPTION_EXCLUDE, "Comma separated list of ports to skip"}, + {OPTION_XUNIT, "File to output results in XUnit format (internal)"}, + }}; + + static constexpr std::array<CommandSwitch, 1> CI_SWITCHES = { + {{OPTION_DRY_RUN, "Print out plan without execution"}}}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("ci x64-windows"), + 1, + SIZE_MAX, + {CI_SWITCHES, CI_SETTINGS}, + nullptr, + }; + + UnknownCIPortsResults find_unknown_ports_for_ci(const VcpkgPaths& paths, + const std::set<std::string>& exclusions, + const Dependencies::PortFileProvider& provider, + const std::vector<FeatureSpec>& fspecs) + { + UnknownCIPortsResults ret; + + auto& fs = paths.get_filesystem(); + + std::map<PackageSpec, std::string> abi_tag_map; + std::set<PackageSpec> will_fail; + + const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::YES, + Build::CleanPackages::YES}; + + vcpkg::Cache<Triplet, Build::PreBuildInfo> pre_build_info_cache; + + auto action_plan = Dependencies::create_feature_install_plan(provider, fspecs, StatusParagraphs{}); + + for (auto&& action : action_plan) + { + if (auto p = action.install_action.get()) + { + // determine abi tag + std::string abi; + if (auto scf = p->source_control_file.get()) + { + auto triplet = p->spec.triplet(); + + const Build::BuildPackageConfig build_config{ + *scf, triplet, paths.port_dir(p->spec), install_plan_options, p->feature_list}; + + auto dependency_abis = + Util::fmap(p->computed_dependencies, [&](const PackageSpec& spec) -> Build::AbiEntry { + auto it = abi_tag_map.find(spec); + + if (it == abi_tag_map.end()) + return {spec.name(), ""}; + else + return {spec.name(), it->second}; + }); + const auto& pre_build_info = pre_build_info_cache.get_lazy( + triplet, [&]() { return Build::PreBuildInfo::from_triplet_file(paths, triplet); }); + + auto maybe_tag_and_file = + Build::compute_abi_tag(paths, build_config, pre_build_info, dependency_abis); + if (auto tag_and_file = maybe_tag_and_file.get()) + { + abi = tag_and_file->tag; + abi_tag_map.emplace(p->spec, abi); + } + } + else if (auto ipv = p->installed_package.get()) + { + abi = ipv->core->package.abi; + if (!abi.empty()) abi_tag_map.emplace(p->spec, abi); + } + + std::string state; + + auto archives_root_dir = paths.root / "archives"; + auto archive_name = abi + ".zip"; + auto archive_subpath = fs::u8path(abi.substr(0, 2)) / archive_name; + auto archive_path = archives_root_dir / archive_subpath; + auto archive_tombstone_path = archives_root_dir / "fail" / archive_subpath; + + bool b_will_build = false; + + if (Util::Sets::contains(exclusions, p->spec.name())) + { + ret.known.emplace(p->spec, BuildResult::EXCLUDED); + will_fail.emplace(p->spec); + } + else if (std::any_of(p->computed_dependencies.begin(), + p->computed_dependencies.end(), + [&](const PackageSpec& spec) { return Util::Sets::contains(will_fail, spec); })) + { + ret.known.emplace(p->spec, BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES); + will_fail.emplace(p->spec); + } + else if (fs.exists(archive_path)) + { + state += "pass"; + ret.known.emplace(p->spec, BuildResult::SUCCEEDED); + } + else if (fs.exists(archive_tombstone_path)) + { + state += "fail"; + ret.known.emplace(p->spec, BuildResult::BUILD_FAILED); + will_fail.emplace(p->spec); + } + else + { + ret.unknown.push_back(p->spec); + b_will_build = true; + } + + System::println("%40s: %1s %8s: %s", p->spec, (b_will_build ? "*" : " "), state, abi); + } + } + + return ret; + } + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + if (!GlobalState::g_binary_caching) + { + System::println(System::Color::warning, "Warning: Running ci without binary caching!"); + } + + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + std::set<std::string> exclusions_set; + auto it_exclusions = options.settings.find(OPTION_EXCLUDE); + if (it_exclusions != options.settings.end()) + { + auto exclusions = Strings::split(it_exclusions->second, ","); + exclusions_set.insert(exclusions.begin(), exclusions.end()); + } + + auto is_dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); + + std::vector<Triplet> triplets; + for (const std::string& triplet : args.command_arguments) + { + triplets.push_back(Triplet::from_canonical_name(triplet)); + } + + if (triplets.empty()) + { + triplets.push_back(default_triplet); + } + + StatusParagraphs status_db = database_load_check(paths); + const auto& paths_port_file = Dependencies::PathsPortFileProvider(paths); + + const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::YES, + Build::CleanPackages::YES}; + + std::vector<std::map<PackageSpec, BuildResult>> all_known_results; + + std::vector<std::string> all_ports = Install::get_all_port_names(paths); + std::vector<TripletAndSummary> results; + for (const Triplet& triplet : triplets) + { + Input::check_triplet(triplet, paths); + + std::vector<PackageSpec> specs = PackageSpec::to_package_specs(all_ports, triplet); + // Install the default features for every package + auto all_fspecs = Util::fmap(specs, [](auto& spec) { return FeatureSpec(spec, ""); }); + auto split_specs = find_unknown_ports_for_ci(paths, exclusions_set, paths_port_file, all_fspecs); + auto fspecs = Util::fmap(split_specs.unknown, [](auto& spec) { return FeatureSpec(spec, ""); }); + + auto action_plan = Dependencies::create_feature_install_plan(paths_port_file, fspecs, status_db); + + for (auto&& action : action_plan) + { + if (auto p = action.install_action.get()) + { + p->build_options = install_plan_options; + if (Util::Sets::contains(exclusions_set, p->spec.name())) + { + p->plan_type = InstallPlanType::EXCLUDED; + } + } + } + + if (is_dry_run) + { + Dependencies::print_plan(action_plan); + } + else + { + auto summary = Install::perform(action_plan, Install::KeepGoing::YES, paths, status_db); + for (auto&& result : summary.results) + split_specs.known.erase(result.spec); + results.push_back({triplet, std::move(summary)}); + all_known_results.emplace_back(std::move(split_specs.known)); + } + } + + for (auto&& result : results) + { + System::println("\nTriplet: %s", result.triplet); + System::println("Total elapsed time: %s", result.summary.total_elapsed_time); + result.summary.print(); + } + + auto it_xunit = options.settings.find(OPTION_XUNIT); + if (it_xunit != options.settings.end()) + { + std::string xunit_doc = "<assemblies><assembly><collection>\n"; + + for (auto&& result : results) + xunit_doc += result.summary.xunit_results(); + for (auto&& known_result : all_known_results) + { + for (auto&& result : known_result) + { + xunit_doc += + Install::InstallSummary::xunit_result(result.first, Chrono::ElapsedTime{}, result.second); + } + } + + xunit_doc += "</collection></assembly></assemblies>\n"; + paths.get_filesystem().write_contents(fs::u8path(it_xunit->second), xunit_doc); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.contact.cpp b/toolsrc/src/vcpkg/commands.contact.cpp new file mode 100644 index 000000000..9f86a67dc --- /dev/null +++ b/toolsrc/src/vcpkg/commands.contact.cpp @@ -0,0 +1,60 @@ +#include "pch.h" + +#include <vcpkg/base/chrono.h> +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/userconfig.h> + +namespace vcpkg::Commands::Contact +{ + const std::string& email() + { + static const std::string S_EMAIL = R"(vcpkg@microsoft.com)"; + return S_EMAIL; + } + + static constexpr StringLiteral OPTION_SURVEY = "--survey"; + + static constexpr std::array<CommandSwitch, 1> SWITCHES = {{ + {OPTION_SURVEY, "Launch default browser to the current vcpkg survey"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("contact"), + 0, + 0, + {SWITCHES, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args) + { + const ParsedArguments parsed_args = args.parse_arguments(COMMAND_STRUCTURE); + + if (Util::Sets::contains(parsed_args.switches, SWITCHES[0].name)) + { + auto maybe_now = Chrono::CTime::get_current_date_time(); + if (const auto p_now = maybe_now.get()) + { + auto& fs = Files::get_real_filesystem(); + auto config = UserConfig::try_read_data(fs); + config.last_completed_survey = p_now->to_string(); + config.try_write_data(fs); + } + +#if defined(_WIN32) + System::cmd_execute("start https://aka.ms/NPS_vcpkg"); + System::println("Default browser launched to https://aka.ms/NPS_vcpkg; thank you for your feedback!"); +#else + System::println( + "Please navigate to https://aka.ms/NPS_vcpkg in your preferred browser. Thank you for your feedback!"); +#endif + } + else + { + System::println("Send an email to %s with any feedback.", email()); + } + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/commands_available_commands.cpp b/toolsrc/src/vcpkg/commands.cpp index 87cc43dca..09da57705 100644 --- a/toolsrc/src/commands_available_commands.cpp +++ b/toolsrc/src/vcpkg/commands.cpp @@ -1,16 +1,23 @@ #include "pch.h" -#include "vcpkg_Commands.h" +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/export.h> +#include <vcpkg/help.h> +#include <vcpkg/install.h> +#include <vcpkg/remove.h> +#include <vcpkg/update.h> namespace vcpkg::Commands { - const std::vector<PackageNameAndFunction<CommandTypeA>>& get_available_commands_type_a() + Span<const PackageNameAndFunction<CommandTypeA>> get_available_commands_type_a() { static std::vector<PackageNameAndFunction<CommandTypeA>> t = { {"install", &Install::perform_and_exit}, {"ci", &CI::perform_and_exit}, {"remove", &Remove::perform_and_exit}, - {"build", &BuildCommand::perform_and_exit}, + {"upgrade", &Upgrade::perform_and_exit}, + {"build", &Build::Command::perform_and_exit}, {"env", &Env::perform_and_exit}, {"build-external", &BuildExternal::perform_and_exit}, {"export", &Export::perform_and_exit}, @@ -18,7 +25,7 @@ namespace vcpkg::Commands return t; } - const std::vector<PackageNameAndFunction<CommandTypeB>>& get_available_commands_type_b() + Span<const PackageNameAndFunction<CommandTypeB>> get_available_commands_type_b() { static std::vector<PackageNameAndFunction<CommandTypeB>> t = { {"/?", &Help::perform_and_exit}, @@ -34,16 +41,18 @@ namespace vcpkg::Commands {"import", &Import::perform_and_exit}, {"cache", &Cache::perform_and_exit}, {"portsdiff", &PortsDiff::perform_and_exit}, + {"autocomplete", &Autocomplete::perform_and_exit}, + {"hash", &Hash::perform_and_exit}, + {"fetch", &Fetch::perform_and_exit}, }; return t; } - const std::vector<PackageNameAndFunction<CommandTypeC>>& get_available_commands_type_c() + Span<const PackageNameAndFunction<CommandTypeC>> get_available_commands_type_c() { static std::vector<PackageNameAndFunction<CommandTypeC>> t = { {"version", &Version::perform_and_exit}, {"contact", &Contact::perform_and_exit}, - {"hash", &Hash::perform_and_exit}, }; return t; } diff --git a/toolsrc/src/commands_create.cpp b/toolsrc/src/vcpkg/commands.create.cpp index 7f85b776a..dfb3ab784 100644 --- a/toolsrc/src/commands_create.cpp +++ b/toolsrc/src/vcpkg/commands.create.cpp @@ -1,26 +1,30 @@ #include "pch.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Files.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> namespace vcpkg::Commands::Create { + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string( + R"###(create zlib2 http://zlib.net/zlib1211.zip "zlib1211-2.zip")###"), + 2, + 3, + {}, + nullptr, + }; + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const std::string example = Commands::Help::create_example_string( - R"###(create zlib2 http://zlib.net/zlib1211.zip "zlib1211-2.zip")###"); - args.check_max_arg_count(3, example); - args.check_min_arg_count(2, example); - args.check_and_get_optional_command_arguments({}); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const std::string port_name = args.command_arguments.at(0); const std::string url = args.command_arguments.at(1); - const fs::path& cmake_exe = paths.get_cmake_exe(); + const fs::path& cmake_exe = paths.get_tool_exe(Tools::CMAKE); - std::vector<CMakeVariable> cmake_args{{L"CMD", L"CREATE"}, {L"PORT", port_name}, {L"URL", url}}; + std::vector<System::CMakeVariable> cmake_args{{"CMD", "CREATE"}, {"PORT", port_name}, {"URL", url}}; if (args.command_arguments.size() >= 3) { @@ -30,10 +34,10 @@ namespace vcpkg::Commands::Create R"(Filename cannot contain invalid chars %s, but was %s)", Files::FILESYSTEM_INVALID_CHARACTERS, zip_file_name); - cmake_args.push_back({L"FILENAME", zip_file_name}); + cmake_args.emplace_back("FILENAME", zip_file_name); } - const std::wstring cmd_launch_cmake = make_cmake_cmd(cmake_exe, paths.ports_cmake, cmake_args); + const std::string cmd_launch_cmake = make_cmake_cmd(cmake_exe, paths.ports_cmake, cmake_args); Checks::exit_with_code(VCPKG_LINE_INFO, System::cmd_execute_clean(cmd_launch_cmake)); } } diff --git a/toolsrc/src/vcpkg/commands.dependinfo.cpp b/toolsrc/src/vcpkg/commands.dependinfo.cpp new file mode 100644 index 000000000..1ca658216 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.dependinfo.cpp @@ -0,0 +1,61 @@ +#include "pch.h" + +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> + +namespace vcpkg::Commands::DependInfo +{ + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string(R"###(depend-info [pat])###"), + 0, + 1, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + std::vector<std::unique_ptr<SourceControlFile>> source_control_files = + Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); + + if (args.command_arguments.size() == 1) + { + const std::string filter = args.command_arguments.at(0); + + Util::erase_remove_if(source_control_files, + [&](const std::unique_ptr<SourceControlFile>& source_control_file) { + const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; + + if (Strings::case_insensitive_ascii_contains(source_paragraph.name, filter)) + { + return false; + } + + for (const Dependency& dependency : source_paragraph.depends) + { + if (Strings::case_insensitive_ascii_contains(dependency.name(), filter)) + { + return false; + } + } + + return true; + }); + } + + for (auto&& source_control_file : source_control_files) + { + const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; + const auto s = Strings::join(", ", source_paragraph.depends, [](const Dependency& d) { return d.name(); }); + System::println("%s: %s", source_paragraph.name, s); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.edit.cpp b/toolsrc/src/vcpkg/commands.edit.cpp new file mode 100644 index 000000000..2569c2cea --- /dev/null +++ b/toolsrc/src/vcpkg/commands.edit.cpp @@ -0,0 +1,152 @@ +#include "pch.h" + +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> + +namespace vcpkg::Commands::Edit +{ + static std::vector<fs::path> find_from_registry() + { + std::vector<fs::path> output; + +#if defined(_WIN32) + static const std::array<const char*, 3> REGKEYS = { + R"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{C26E74D1-022E-4238-8B9D-1E7564A36CC9}_is1)", + R"(SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{1287CAD5-7C8D-410D-88B9-0D1EE4A83FF2}_is1)", + R"(SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{F8A2A208-72B3-4D61-95FC-8A65D340689B}_is1)", + }; + + for (auto&& keypath : REGKEYS) + { + const Optional<std::string> code_installpath = + System::get_registry_string(HKEY_LOCAL_MACHINE, keypath, "InstallLocation"); + if (const auto c = code_installpath.get()) + { + const fs::path install_path = fs::path(*c); + output.push_back(install_path / "Code - Insiders.exe"); + output.push_back(install_path / "Code.exe"); + } + } +#endif + return output; + } + + static constexpr StringLiteral OPTION_BUILDTREES = "--buildtrees"; + + static constexpr StringLiteral OPTION_ALL = "--all"; + + static std::vector<std::string> valid_arguments(const VcpkgPaths& paths) + { + auto sources_and_errors = Paragraphs::try_load_all_ports(paths.get_filesystem(), paths.ports); + + return Util::fmap(sources_and_errors.paragraphs, + [](auto&& pgh) -> std::string { return pgh->core_paragraph->name; }); + } + + static constexpr std::array<CommandSwitch, 2> EDIT_SWITCHES = { + {{OPTION_BUILDTREES, "Open editor into the port-specific buildtree subfolder"}, + {OPTION_ALL, "Open editor into the port as well as the port-specific buildtree subfolder"}}}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("edit zlib"), + 1, + 10, + {EDIT_SWITCHES, {}}, + &valid_arguments, + }; + + static std::vector<std::string> create_editor_arguments(const VcpkgPaths& paths, + const ParsedArguments& options, + const std::vector<std::string>& ports) + { + if (Util::Sets::contains(options.switches, OPTION_ALL)) + { + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { + const auto portpath = paths.ports / port_name; + const auto portfile = portpath / "portfile.cmake"; + const auto buildtrees_current_dir = paths.buildtrees / port_name; + return Strings::format(R"###("%s" "%s" "%s")###", + portpath.u8string(), + portfile.u8string(), + buildtrees_current_dir.u8string()); + }); + } + + if (Util::Sets::contains(options.switches, OPTION_BUILDTREES)) + { + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { + const auto buildtrees_current_dir = paths.buildtrees / port_name; + return Strings::format(R"###("%s")###", buildtrees_current_dir.u8string()); + }); + } + + return Util::fmap(ports, [&](const std::string& port_name) -> std::string { + const auto portpath = paths.ports / port_name; + const auto portfile = portpath / "portfile.cmake"; + return Strings::format(R"###("%s" "%s")###", portpath.u8string(), portfile.u8string()); + }); + } + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + static const fs::path VS_CODE_INSIDERS = fs::path{"Microsoft VS Code Insiders"} / "Code - Insiders.exe"; + static const fs::path VS_CODE = fs::path{"Microsoft VS Code"} / "Code.exe"; + + auto& fs = paths.get_filesystem(); + + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + const std::vector<std::string>& ports = args.command_arguments; + for (auto&& port_name : ports) + { + const fs::path portpath = paths.ports / port_name; + Checks::check_exit( + VCPKG_LINE_INFO, fs.is_directory(portpath), R"(Could not find port named "%s")", port_name); + } + + std::vector<fs::path> candidate_paths; + auto maybe_editor_path = System::get_environment_variable("EDITOR"); + if (const std::string* editor_path = maybe_editor_path.get()) + { + candidate_paths.emplace_back(*editor_path); + } + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const fs::path* pf = program_files.get()) + { + candidate_paths.push_back(*pf / VS_CODE_INSIDERS); + candidate_paths.push_back(*pf / VS_CODE); + } + + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const fs::path* pf = program_files_32_bit.get()) + { + candidate_paths.push_back(*pf / VS_CODE_INSIDERS); + candidate_paths.push_back(*pf / VS_CODE); + } + + const std::vector<fs::path> from_registry = find_from_registry(); + candidate_paths.insert(candidate_paths.end(), from_registry.cbegin(), from_registry.cend()); + + const auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { return fs.exists(p); }); + if (it == candidate_paths.cend()) + { + System::println( + System::Color::error, + "Error: Visual Studio Code was not found and the environment variable EDITOR is not set or invalid."); + System::println("The following paths were examined:"); + Files::print_paths(candidate_paths); + System::println("You can also set the environmental variable EDITOR to your editor of choice."); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + const fs::path env_editor = *it; + const std::vector<std::string> arguments = create_editor_arguments(paths, options, ports); + const auto args_as_string = Strings::join(" ", arguments); + const auto cmd_line = Strings::format(R"("%s" %s -n)", env_editor.u8string(), args_as_string); + Checks::exit_with_code(VCPKG_LINE_INFO, System::cmd_execute(cmd_line)); + } +} diff --git a/toolsrc/src/vcpkg/commands.env.cpp b/toolsrc/src/vcpkg/commands.env.cpp new file mode 100644 index 000000000..d078baedb --- /dev/null +++ b/toolsrc/src/vcpkg/commands.env.cpp @@ -0,0 +1,74 @@ +#include "pch.h" + +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> + +namespace vcpkg::Commands::Env +{ + static constexpr StringLiteral OPTION_BIN = "--bin"; + static constexpr StringLiteral OPTION_INCLUDE = "--include"; + static constexpr StringLiteral OPTION_DEBUG_BIN = "--debug-bin"; + static constexpr StringLiteral OPTION_TOOLS = "--tools"; + static constexpr StringLiteral OPTION_PYTHON = "--python"; + + static constexpr std::array<CommandSwitch, 5> SWITCHES = {{ + {OPTION_BIN, "Add installed bin/ to PATH"}, + {OPTION_INCLUDE, "Add installed include/ to INCLUDE"}, + {OPTION_DEBUG_BIN, "Add installed debug/bin/ to PATH"}, + {OPTION_TOOLS, "Add installed tools/*/ to PATH"}, + {OPTION_PYTHON, "Add installed python/ to PYTHONPATH"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("env --triplet x64-windows"), + 0, + 0, + {SWITCHES, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& triplet) + { + const auto& fs = paths.get_filesystem(); + + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + const auto pre_build_info = Build::PreBuildInfo::from_triplet_file(paths, triplet); + const Toolset& toolset = paths.get_toolset(pre_build_info); + auto env_cmd = Build::make_build_env_cmd(pre_build_info, toolset); + + std::unordered_map<std::string, std::string> extra_env = {}; + const bool add_bin = Util::Sets::contains(options.switches, OPTION_BIN); + const bool add_include = Util::Sets::contains(options.switches, OPTION_INCLUDE); + const bool add_debug_bin = Util::Sets::contains(options.switches, OPTION_DEBUG_BIN); + const bool add_tools = Util::Sets::contains(options.switches, OPTION_TOOLS); + const bool add_python = Util::Sets::contains(options.switches, OPTION_PYTHON); + + std::vector<std::string> path_vars; + if (add_bin) path_vars.push_back((paths.installed / triplet.to_string() / "bin").u8string()); + if (add_debug_bin) path_vars.push_back((paths.installed / triplet.to_string() / "debug" / "bin").u8string()); + if (add_include) extra_env.emplace("INCLUDE", (paths.installed / triplet.to_string() / "include").u8string()); + if (add_tools) + { + auto tools_dir = paths.installed / triplet.to_string() / "tools"; + auto tool_files = fs.get_files_non_recursive(tools_dir); + path_vars.push_back(tools_dir.u8string()); + for (auto&& tool_dir : tool_files) + { + if (fs.is_directory(tool_dir)) path_vars.push_back(tool_dir.u8string()); + } + } + if (add_python) extra_env.emplace("PYTHONPATH", (paths.installed / triplet.to_string() / "python").u8string()); + if (path_vars.size() > 0) extra_env.emplace("PATH", Strings::join(";", path_vars)); + + if (env_cmd.empty()) + System::cmd_execute_clean("cmd", extra_env); + else + System::cmd_execute_clean(env_cmd + " && cmd", extra_env); + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.exportifw.cpp b/toolsrc/src/vcpkg/commands.exportifw.cpp new file mode 100644 index 000000000..ae106196a --- /dev/null +++ b/toolsrc/src/vcpkg/commands.exportifw.cpp @@ -0,0 +1,487 @@ +#include "pch.h" + +#include <vcpkg/commands.h> +#include <vcpkg/export.h> +#include <vcpkg/export.ifw.h> +#include <vcpkg/install.h> + +namespace vcpkg::Export::IFW +{ + using Dependencies::ExportPlanAction; + using Dependencies::ExportPlanType; + using Install::InstallDir; + + static std::string create_release_date() + { + const tm date_time = System::get_current_date_time(); + + // Format is: YYYY-mm-dd + // 10 characters + 1 null terminating character will be written for a total of 11 chars + char mbstr[11]; + const size_t bytes_written = std::strftime(mbstr, sizeof(mbstr), "%Y-%m-%d", &date_time); + Checks::check_exit(VCPKG_LINE_INFO, + bytes_written == 10, + "Expected 10 bytes to be written, but %u were written", + bytes_written); + const std::string date_time_as_string(mbstr); + return date_time_as_string; + } + + std::string safe_rich_from_plain_text(const std::string& text) + { + // match standalone ampersand, no HTML number or name + std::regex standalone_ampersand(R"###(&(?!(#[0-9]+|\w+);))###"); + + return std::regex_replace(text, standalone_ampersand, "&"); + } + + fs::path get_packages_dir_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + return ifw_options.maybe_packages_dir_path.has_value() + ? fs::path(ifw_options.maybe_packages_dir_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-packages"); + } + + fs::path get_repository_dir_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + return ifw_options.maybe_repository_dir_path.has_value() + ? fs::path(ifw_options.maybe_repository_dir_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-repository"); + } + + fs::path get_config_file_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + return ifw_options.maybe_config_file_path.has_value() + ? fs::path(ifw_options.maybe_config_file_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-configuration.xml"); + } + + fs::path get_installer_file_path(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + return ifw_options.maybe_installer_file_path.has_value() + ? fs::path(ifw_options.maybe_installer_file_path.value_or_exit(VCPKG_LINE_INFO)) + : paths.root / (export_id + "-ifw-installer.exe"); + } + + fs::path export_real_package(const fs::path& ifw_packages_dir_path, + const ExportPlanAction& action, + Files::Filesystem& fs) + { + std::error_code ec; + + const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + + // Prepare meta dir + const fs::path package_xml_file_path = + ifw_packages_dir_path / + Strings::format("packages.%s.%s", action.spec.name(), action.spec.triplet().canonical_name()) / "meta" / + "package.xml"; + const fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + + auto deps = Strings::join( + ",", binary_paragraph.depends, [](const std::string& dep) { return "packages." + dep + ":"; }); + + if (!deps.empty()) deps = "\n <Dependencies>" + deps + "</Dependencies>"; + + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>%s</DisplayName> + <Version>%s</Version> + <ReleaseDate>%s</ReleaseDate> + <AutoDependOn>packages.%s:,triplets.%s:</AutoDependOn>%s + <Virtual>true</Virtual> +</Package> +)###", + action.spec.to_string(), + binary_paragraph.version, + create_release_date(), + action.spec.name(), + action.spec.triplet().canonical_name(), + deps)); + + // Return dir path for export package data + return ifw_packages_dir_path / + Strings::format("packages.%s.%s", action.spec.name(), action.spec.triplet().canonical_name()) / "data" / + "installed"; + } + + void export_unique_packages(const fs::path& raw_exported_dir_path, + std::map<std::string, const ExportPlanAction*> unique_packages, + Files::Filesystem& fs) + { + std::error_code ec; + + // packages + + fs::path package_xml_file_path = raw_exported_dir_path / "packages" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>Packages</DisplayName> + <Version>1.0.0</Version> + <ReleaseDate>%s</ReleaseDate> +</Package> +)###", + create_release_date())); + + for (const auto& unique_package : unique_packages) + { + const ExportPlanAction& action = *(unique_package.second); + const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + + package_xml_file_path = + raw_exported_dir_path / Strings::format("packages.%s", unique_package.first) / "meta" / "package.xml"; + package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>%s</DisplayName> + <Description>%s</Description> + <Version>%s</Version> + <ReleaseDate>%s</ReleaseDate> +</Package> +)###", + action.spec.name(), + safe_rich_from_plain_text(binary_paragraph.description), + binary_paragraph.version, + create_release_date())); + } + } + + void export_unique_triplets(const fs::path& raw_exported_dir_path, + std::set<std::string> unique_triplets, + Files::Filesystem& fs) + { + std::error_code ec; + + // triplets + + fs::path package_xml_file_path = raw_exported_dir_path / "triplets" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>Triplets</DisplayName> + <Version>1.0.0</Version> + <ReleaseDate>%s</ReleaseDate> +</Package> +)###", + create_release_date())); + + for (const std::string& triplet : unique_triplets) + { + package_xml_file_path = + raw_exported_dir_path / Strings::format("triplets.%s", triplet) / "meta" / "package.xml"; + package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>%s</DisplayName> + <Version>1.0.0</Version> + <ReleaseDate>%s</ReleaseDate> +</Package> +)###", + triplet, + create_release_date())); + } + } + + void export_integration(const fs::path& raw_exported_dir_path, Files::Filesystem& fs) + { + std::error_code ec; + + // integration + fs::path package_xml_file_path = raw_exported_dir_path / "integration" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>Integration</DisplayName> + <Version>1.0.0</Version> + <ReleaseDate>%s</ReleaseDate> +</Package> +)###", + create_release_date())); + } + + void export_config(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + std::error_code ec; + Files::Filesystem& fs = paths.get_filesystem(); + + const fs::path config_xml_file_path = get_config_file_path(export_id, ifw_options, paths); + + fs::path config_xml_dir_path = config_xml_file_path.parent_path(); + fs.create_directories(config_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for configuration file %s", + config_xml_file_path.generic_string()); + + std::string formatted_repo_url; + std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); + if (!ifw_repo_url.empty()) + { + formatted_repo_url = Strings::format(R"###( + <RemoteRepositories> + <Repository> + <Url>%s</Url> + </Repository> + </RemoteRepositories>)###", + ifw_repo_url); + } + + fs.write_contents(config_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Installer> + <Name>vcpkg</Name> + <Version>1.0.0</Version> + <StartMenuDir>vcpkg</StartMenuDir> + <TargetDir>@RootDir@/src/vcpkg</TargetDir>%s +</Installer> +)###", + formatted_repo_url)); + } + + void export_maintenance_tool(const fs::path& ifw_packages_dir_path, const VcpkgPaths& paths) + { + System::println("Exporting maintenance tool... "); + + std::error_code ec; + Files::Filesystem& fs = paths.get_filesystem(); + + const fs::path& installerbase_exe = paths.get_tool_exe(Tools::IFW_INSTALLER_BASE); + fs::path tempmaintenancetool = ifw_packages_dir_path / "maintenance" / "data" / "tempmaintenancetool.exe"; + fs.create_directories(tempmaintenancetool.parent_path(), ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + tempmaintenancetool.generic_string()); + fs.copy_file(installerbase_exe, tempmaintenancetool, fs::copy_options::overwrite_existing, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not write package file %s", tempmaintenancetool.generic_string()); + + fs::path package_xml_file_path = ifw_packages_dir_path / "maintenance" / "meta" / "package.xml"; + fs::path package_xml_dir_path = package_xml_file_path.parent_path(); + fs.create_directories(package_xml_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not create directory for package file %s", + package_xml_file_path.generic_string()); + fs.write_contents(package_xml_file_path, + Strings::format( + R"###(<?xml version="1.0"?> +<Package> + <DisplayName>Maintenance Tool</DisplayName> + <Description>Maintenance Tool</Description> + <Version>1.0.0</Version> + <ReleaseDate>%s</ReleaseDate> + <Script>maintenance.qs</Script> + <Essential>true</Essential> + <Virtual>true</Virtual> + <ForcedInstallation>true</ForcedInstallation> +</Package> +)###", + create_release_date())); + const fs::path script_source = paths.root / "scripts" / "ifw" / "maintenance.qs"; + const fs::path script_destination = ifw_packages_dir_path / "maintenance" / "meta" / "maintenance.qs"; + fs.copy_file(script_source, script_destination, fs::copy_options::overwrite_existing, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not write package file %s", script_destination.generic_string()); + + System::println("Exporting maintenance tool... done"); + } + + void do_repository(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + const fs::path& repogen_exe = paths.get_tool_exe(Tools::IFW_REPOGEN); + const fs::path packages_dir = get_packages_dir_path(export_id, ifw_options, paths); + const fs::path repository_dir = get_repository_dir_path(export_id, ifw_options, paths); + + System::println("Generating repository %s...", repository_dir.generic_string()); + + std::error_code ec; + Files::Filesystem& fs = paths.get_filesystem(); + + fs.remove_all(repository_dir, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not remove outdated repository directory %s", repository_dir.generic_string()); + + const auto cmd_line = Strings::format(R"("%s" --packages "%s" "%s" > nul)", + repogen_exe.u8string(), + packages_dir.u8string(), + repository_dir.u8string()); + + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: IFW repository generating failed"); + + System::println(System::Color::success, "Generating repository %s... done.", repository_dir.generic_string()); + } + + void do_installer(const std::string& export_id, const Options& ifw_options, const VcpkgPaths& paths) + { + const fs::path& binarycreator_exe = paths.get_tool_exe(Tools::IFW_BINARYCREATOR); + const fs::path config_file = get_config_file_path(export_id, ifw_options, paths); + const fs::path packages_dir = get_packages_dir_path(export_id, ifw_options, paths); + const fs::path repository_dir = get_repository_dir_path(export_id, ifw_options, paths); + const fs::path installer_file = get_installer_file_path(export_id, ifw_options, paths); + + System::println("Generating installer %s...", installer_file.generic_string()); + + std::string cmd_line; + + std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); + if (!ifw_repo_url.empty()) + { + cmd_line = Strings::format(R"("%s" --online-only --config "%s" --repository "%s" "%s" > nul)", + binarycreator_exe.u8string(), + config_file.u8string(), + repository_dir.u8string(), + installer_file.u8string()); + } + else + { + cmd_line = Strings::format(R"("%s" --config "%s" --packages "%s" "%s" > nul)", + binarycreator_exe.u8string(), + config_file.u8string(), + packages_dir.u8string(), + installer_file.u8string()); + } + + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: IFW installer generating failed"); + + System::println(System::Color::success, "Generating installer %s... done.", installer_file.generic_string()); + } + + void do_export(const std::vector<ExportPlanAction>& export_plan, + const std::string& export_id, + const Options& ifw_options, + const VcpkgPaths& paths) + { + std::error_code ec; + Files::Filesystem& fs = paths.get_filesystem(); + + // Prepare packages directory + const fs::path ifw_packages_dir_path = get_packages_dir_path(export_id, ifw_options, paths); + + fs.remove_all(ifw_packages_dir_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Could not remove outdated packages directory %s", + ifw_packages_dir_path.generic_string()); + + fs.create_directory(ifw_packages_dir_path, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not create packages directory %s", ifw_packages_dir_path.generic_string()); + + // Export maintenance tool + export_maintenance_tool(ifw_packages_dir_path, paths); + + System::println("Exporting packages %s... ", ifw_packages_dir_path.generic_string()); + + // execute the plan + std::map<std::string, const ExportPlanAction*> unique_packages; + std::set<std::string> unique_triplets; + for (const ExportPlanAction& action : export_plan) + { + if (action.plan_type != ExportPlanType::ALREADY_BUILT) + { + Checks::unreachable(VCPKG_LINE_INFO); + } + + const std::string display_name = action.spec.to_string(); + System::println("Exporting package %s... ", display_name); + + const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + + unique_packages[action.spec.name()] = &action; + unique_triplets.insert(action.spec.triplet().canonical_name()); + + // Export real package and return data dir for installation + fs::path ifw_package_dir_path = export_real_package(ifw_packages_dir_path, action, fs); + + // Copy package data + const InstallDir dirs = InstallDir::from_destination_root(ifw_package_dir_path, + action.spec.triplet().to_string(), + ifw_package_dir_path / "vcpkg" / "info" / + (binary_paragraph.fullstem() + ".list")); + + Install::install_files_and_write_listfile(paths.get_filesystem(), paths.package_dir(action.spec), dirs); + System::println("Exporting package %s... done", display_name); + } + + System::println("Exporting packages %s... done", ifw_packages_dir_path.generic_string()); + + const fs::path config_file = get_config_file_path(export_id, ifw_options, paths); + + System::println("Generating configuration %s...", config_file.generic_string()); + + // Unique packages + export_unique_packages(ifw_packages_dir_path, unique_packages, fs); + + // Unique triplets + export_unique_triplets(ifw_packages_dir_path, unique_triplets, fs); + + // Copy files needed for integration + export_integration_files(ifw_packages_dir_path / "integration" / "data", paths); + // Integration + export_integration(ifw_packages_dir_path, fs); + + // Configuration + export_config(export_id, ifw_options, paths); + + System::println("Generating configuration %s... done.", config_file.generic_string()); + + // Do repository (optional) + std::string ifw_repo_url = ifw_options.maybe_repository_url.value_or(""); + if (!ifw_repo_url.empty()) + { + do_repository(export_id, ifw_options, paths); + } + + // Do installer + do_installer(export_id, ifw_options, paths); + } +} diff --git a/toolsrc/src/vcpkg/commands.fetch.cpp b/toolsrc/src/vcpkg/commands.fetch.cpp new file mode 100644 index 000000000..33c5c1dcc --- /dev/null +++ b/toolsrc/src/vcpkg/commands.fetch.cpp @@ -0,0 +1,965 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/sortedvector.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> + +namespace vcpkg::Commands::Fetch +{ + static constexpr CStringView V_120 = "v120"; + static constexpr CStringView V_140 = "v140"; + static constexpr CStringView V_141 = "v141"; + + struct ToolData + { + std::array<int, 3> version; + fs::path exe_path; + std::string url; + fs::path download_path; + bool is_archive; + fs::path tool_dir_path; + std::string sha512; + }; + + static Optional<std::array<int, 3>> parse_version_string(const std::string& version_as_string) + { + static const std::regex RE(R"###((\d+)\.(\d+)\.(\d+))###"); + + std::match_results<std::string::const_iterator> match; + const auto found = std::regex_search(version_as_string, match, RE); + if (!found) + { + return {}; + } + + const int d1 = atoi(match[1].str().c_str()); + const int d2 = atoi(match[2].str().c_str()); + const int d3 = atoi(match[3].str().c_str()); + const std::array<int, 3> result = {d1, d2, d3}; + return result; + } + + struct VcpkgStringRange + { + VcpkgStringRange() = default; + + // Implicit by design + VcpkgStringRange(const std::string& s) : begin(s.cbegin()), end(s.cend()) {} + + VcpkgStringRange(const std::string::const_iterator begin, const std::string::const_iterator end) + : begin(begin), end(end) + { + } + + std::string::const_iterator begin; + std::string::const_iterator end; + + std::string to_string() const { return std::string(this->begin, this->end); } + }; + + static std::vector<VcpkgStringRange> find_all_enclosed(const VcpkgStringRange& input, + const std::string& left_delim, + const std::string& right_delim) + { + std::string::const_iterator it_left = input.begin; + std::string::const_iterator it_right = input.begin; + + std::vector<VcpkgStringRange> output; + + while (true) + { + it_left = std::search(it_right, input.end, left_delim.cbegin(), left_delim.cend()); + if (it_left == input.end) break; + + it_left += left_delim.length(); + + it_right = std::search(it_left, input.end, right_delim.cbegin(), right_delim.cend()); + if (it_right == input.end) break; + + output.emplace_back(it_left, it_right); + + ++it_right; + } + + return output; + } + + static VcpkgStringRange find_exactly_one_enclosed(const VcpkgStringRange& input, + const std::string& left_tag, + const std::string& right_tag) + { + std::vector<VcpkgStringRange> result = find_all_enclosed(input, left_tag, right_tag); + Checks::check_exit(VCPKG_LINE_INFO, + result.size() == 1, + "Found %d sets of %s.*%s but expected exactly 1, in block:\n%s", + result.size(), + left_tag, + right_tag, + input); + return std::move(result.front()); + } + + static Optional<VcpkgStringRange> find_at_most_one_enclosed(const VcpkgStringRange& input, + const std::string& left_tag, + const std::string& right_tag) + { + std::vector<VcpkgStringRange> result = find_all_enclosed(input, left_tag, right_tag); + Checks::check_exit(VCPKG_LINE_INFO, + result.size() <= 1, + "Found %d sets of %s.*%s but expected at most 1, in block:\n%s", + result.size(), + left_tag, + right_tag, + input); + + if (result.empty()) + { + return nullopt; + } + + return result.front(); + } + + static ToolData parse_tool_data_from_xml(const VcpkgPaths& paths, const std::string& tool) + { +#if defined(_WIN32) + static constexpr StringLiteral OS_STRING = "windows"; +#elif defined(__APPLE__) + static constexpr StringLiteral OS_STRING = "osx"; +#elif defined(__linux__) + static constexpr StringLiteral OS_STRING = "linux"; +#else + return ToolData{}; +#endif + +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + static const std::string XML_VERSION = "2"; + static const fs::path XML_PATH = paths.scripts / "vcpkgTools.xml"; + static const std::regex XML_VERSION_REGEX{R"###(<tools[\s]+version="([^"]+)">)###"}; + static const std::string XML = paths.get_filesystem().read_contents(XML_PATH).value_or_exit(VCPKG_LINE_INFO); + std::smatch match_xml_version; + const bool has_xml_version = std::regex_search(XML.cbegin(), XML.cend(), match_xml_version, XML_VERSION_REGEX); + Checks::check_exit(VCPKG_LINE_INFO, + has_xml_version, + R"(Could not find <tools version="%s"> in %s)", + XML_VERSION, + XML_PATH.generic_string()); + Checks::check_exit(VCPKG_LINE_INFO, + XML_VERSION == match_xml_version[1], + "Expected %s version: [%s], but was [%s]. Please re-run bootstrap-vcpkg.", + XML_PATH.generic_string(), + XML_VERSION, + match_xml_version[1]); + + const std::regex tool_regex{Strings::format(R"###(<tool[\s]+name="%s"[\s]+os="%s">)###", tool, OS_STRING)}; + std::smatch match_tool_entry; + const bool has_tool_entry = std::regex_search(XML.cbegin(), XML.cend(), match_tool_entry, tool_regex); + Checks::check_exit(VCPKG_LINE_INFO, + has_tool_entry, + "Could not find entry for tool [%s] in %s", + tool, + XML_PATH.generic_string()); + + const std::string tool_data = find_exactly_one_enclosed(XML, match_tool_entry[0], "</tool>").to_string(); + const std::string version_as_string = + find_exactly_one_enclosed(tool_data, "<version>", "</version>").to_string(); + const std::string exe_relative_path = + find_exactly_one_enclosed(tool_data, "<exeRelativePath>", "</exeRelativePath>").to_string(); + const std::string url = find_exactly_one_enclosed(tool_data, "<url>", "</url>").to_string(); + const std::string sha512 = find_exactly_one_enclosed(tool_data, "<sha512>", "</sha512>").to_string(); + auto archive_name = find_at_most_one_enclosed(tool_data, "<archiveName>", "</archiveName>"); + + const Optional<std::array<int, 3>> version = parse_version_string(version_as_string); + Checks::check_exit(VCPKG_LINE_INFO, + version.has_value(), + "Could not parse version for tool %s. Version string was: %s", + tool, + version_as_string); + + const std::string tool_dir_name = Strings::format("%s-%s-%s", tool, version_as_string, OS_STRING); + const fs::path tool_dir_path = paths.tools / tool_dir_name; + const fs::path exe_path = tool_dir_path / exe_relative_path; + + return ToolData{*version.get(), + exe_path, + url, + paths.downloads / archive_name.value_or(exe_relative_path).to_string(), + archive_name.has_value(), + tool_dir_path, + sha512}; +#endif + } + + static bool exists_and_has_equal_or_greater_version(const std::string& version_cmd, + const std::array<int, 3>& expected_version) + { + const auto rc = System::cmd_execute_and_capture_output(Strings::format(R"(%s)", version_cmd)); + if (rc.exit_code != 0) + { + return false; + } + + const Optional<std::array<int, 3>> v = parse_version_string(rc.output); + if (!v.has_value()) + { + return false; + } + + const std::array<int, 3> actual_version = *v.get(); + return (actual_version[0] > expected_version[0] || + (actual_version[0] == expected_version[0] && actual_version[1] > expected_version[1]) || + (actual_version[0] == expected_version[0] && actual_version[1] == expected_version[1] && + actual_version[2] >= expected_version[2])); + } + + static Optional<fs::path> find_if_has_equal_or_greater_version(Files::Filesystem& fs, + const std::vector<fs::path>& candidate_paths, + const std::string& version_check_arguments, + const std::array<int, 3>& expected_version) + { + const auto it = Util::find_if(candidate_paths, [&](const fs::path& p) { + if (!fs.exists(p)) return false; + const std::string cmd = Strings::format(R"("%s" %s)", p.u8string(), version_check_arguments); + return exists_and_has_equal_or_greater_version(cmd, expected_version); + }); + + if (it != candidate_paths.cend()) + { + return *it; + } + + return nullopt; + } + + static void extract_archive(const VcpkgPaths& paths, const fs::path& archive, const fs::path& to_path) + { + Files::Filesystem& fs = paths.get_filesystem(); + const fs::path to_path_partial = to_path.u8string() + ".partial"; + + std::error_code ec; + fs.remove_all(to_path, ec); + fs.remove_all(to_path_partial, ec); + fs.create_directories(to_path_partial, ec); + const auto ext = archive.extension(); +#if defined(_WIN32) + if (ext == ".nupkg") + { + static bool recursion_limiter_sevenzip_old = false; + Checks::check_exit(VCPKG_LINE_INFO, !recursion_limiter_sevenzip_old); + recursion_limiter_sevenzip_old = true; + const auto nuget_exe = get_tool_path(paths, Tools::NUGET); + + const std::string stem = archive.stem().u8string(); + // assuming format of [name].[version in the form d.d.d] + // This assumption may not always hold + std::smatch match; + const bool has_match = std::regex_match(stem, match, std::regex{R"###(^(.+)\.(\d+\.\d+\.\d+)$)###"}); + Checks::check_exit(VCPKG_LINE_INFO, + has_match, + "Could not deduce nuget id and version from filename: %s", + archive.u8string()); + + const std::string nugetid = match[1]; + const std::string version = match[2]; + + const auto code_and_output = System::cmd_execute_and_capture_output(Strings::format( + R"("%s" install %s -Version %s -OutputDirectory "%s" -Source "%s" -nocache -DirectDownload -NonInteractive -ForceEnglishOutput -PackageSaveMode nuspec)", + nuget_exe.u8string(), + nugetid, + version, + to_path_partial.u8string(), + paths.downloads.u8string())); + + Checks::check_exit(VCPKG_LINE_INFO, + code_and_output.exit_code == 0, + "Failed to extract '%s' with message:\n%s", + archive.u8string(), + code_and_output.output); + recursion_limiter_sevenzip_old = false; + } + else + { + static bool recursion_limiter_sevenzip = false; + Checks::check_exit(VCPKG_LINE_INFO, !recursion_limiter_sevenzip); + recursion_limiter_sevenzip = true; + const auto seven_zip = get_tool_path(paths, Tools::SEVEN_ZIP); + const auto code_and_output = System::cmd_execute_and_capture_output(Strings::format( + R"("%s" x "%s" -o"%s" -y)", seven_zip.u8string(), archive.u8string(), to_path_partial.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, + code_and_output.exit_code == 0, + "7zip failed while extracting '%s' with message:\n%s", + archive.u8string(), + code_and_output.output); + recursion_limiter_sevenzip = false; + } +#else + if (ext == ".gz" && ext.extension() != ".tar") + { + const auto code = System::cmd_execute( + Strings::format(R"(cd '%s' && tar xzf '%s')", to_path_partial.u8string(), archive.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "tar failed while extracting %s", archive.u8string()); + } + else if (ext == ".zip") + { + const auto code = System::cmd_execute( + Strings::format(R"(cd '%s' && unzip -qqo '%s')", to_path_partial.u8string(), archive.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "unzip failed while extracting %s", archive.u8string()); + } + else + { + Checks::exit_with_message(VCPKG_LINE_INFO, "Unexpected archive extension: %s", ext.u8string()); + } +#endif + + fs.rename(to_path_partial, to_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Failed to do post-extract rename-in-place.\nfs.rename(%s, %s, %s)", + to_path_partial.u8string(), + to_path.u8string(), + ec.message()); + } + + static void verify_hash(const Files::Filesystem& fs, + const std::string& url, + const fs::path& path, + const std::string& sha512) + { + const std::string actual_hash = Hash::get_file_hash(fs, path, "SHA512"); + Checks::check_exit(VCPKG_LINE_INFO, + sha512 == actual_hash, + "File does not have the expected hash:\n" + " url : [ %s ]\n" + " File path : [ %s ]\n" + " Expected hash : [ %s ]\n" + " Actual hash : [ %s ]\n", + url, + path.u8string(), + sha512, + actual_hash); + } + +#if defined(_WIN32) + static void winhttp_download_file(Files::Filesystem& fs, + CStringView target_file_path, + CStringView hostname, + CStringView url_path) + { + // Make sure the directories are present, otherwise fopen_s fails + const auto dir = fs::path(target_file_path.c_str()).parent_path(); + std::error_code ec; + fs.create_directories(dir, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Could not create directories %s", dir.u8string()); + + FILE* f = nullptr; + const errno_t err = fopen_s(&f, target_file_path.c_str(), "wb"); + Checks::check_exit(VCPKG_LINE_INFO, + !err, + "Could not download https://%s%s. Failed to open file %s. Error code was %s", + hostname, + url_path, + target_file_path, + std::to_string(err)); + + auto hSession = WinHttpOpen( + L"vcpkg/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + Checks::check_exit(VCPKG_LINE_INFO, hSession, "WinHttpOpen() failed: %d", GetLastError()); + + // Use Windows 10 defaults on Windows 7 + DWORD secure_protocols(WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 | + WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 | WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2); + WinHttpSetOption(hSession, WINHTTP_OPTION_SECURE_PROTOCOLS, &secure_protocols, sizeof(secure_protocols)); + + // Specify an HTTP server. + auto hConnect = WinHttpConnect(hSession, Strings::to_utf16(hostname).c_str(), INTERNET_DEFAULT_HTTPS_PORT, 0); + Checks::check_exit(VCPKG_LINE_INFO, hConnect, "WinHttpConnect() failed: %d", GetLastError()); + + // Create an HTTP request handle. + auto hRequest = WinHttpOpenRequest(hConnect, + L"GET", + Strings::to_utf16(url_path).c_str(), + nullptr, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_SECURE); + Checks::check_exit(VCPKG_LINE_INFO, hRequest, "WinHttpOpenRequest() failed: %d", GetLastError()); + + // Send a request. + auto bResults = + WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpSendRequest() failed: %d", GetLastError()); + + // End the request. + bResults = WinHttpReceiveResponse(hRequest, NULL); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReceiveResponse() failed: %d", GetLastError()); + + std::vector<char> buf; + + size_t total_downloaded_size = 0; + DWORD dwSize = 0; + do + { + DWORD downloaded_size = 0; + bResults = WinHttpQueryDataAvailable(hRequest, &dwSize); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpQueryDataAvailable() failed: %d", GetLastError()); + + if (buf.size() < dwSize) buf.resize(dwSize * 2); + + bResults = WinHttpReadData(hRequest, (LPVOID)buf.data(), dwSize, &downloaded_size); + Checks::check_exit(VCPKG_LINE_INFO, bResults, "WinHttpReadData() failed: %d", GetLastError()); + fwrite(buf.data(), 1, downloaded_size, f); + + total_downloaded_size += downloaded_size; + } while (dwSize > 0); + + WinHttpCloseHandle(hSession); + WinHttpCloseHandle(hConnect); + WinHttpCloseHandle(hRequest); + fflush(f); + fclose(f); + } +#endif + + static void download_file(Files::Filesystem& fs, + const std::string& url, + const fs::path& download_path, + const std::string& sha512) + { + const std::string download_path_part = download_path.u8string() + ".part"; + std::error_code ec; + fs.remove(download_path, ec); + fs.remove(download_path_part, ec); +#if defined(_WIN32) + auto url_no_proto = url.substr(8); // drop https:// + auto path_begin = Util::find(url_no_proto, '/'); + std::string hostname(url_no_proto.begin(), path_begin); + std::string path(path_begin, url_no_proto.end()); + + winhttp_download_file(fs, download_path_part.c_str(), hostname, path); +#else + const auto code = System::cmd_execute( + Strings::format(R"(curl -L '%s' --create-dirs --output '%s')", url, download_path_part)); + Checks::check_exit(VCPKG_LINE_INFO, code == 0, "Could not download %s", url); +#endif + + verify_hash(fs, url, download_path_part, sha512); + fs.rename(download_path_part, download_path, ec); + Checks::check_exit(VCPKG_LINE_INFO, + !ec, + "Failed to do post-download rename-in-place.\nfs.rename(%s, %s, %s)", + download_path_part, + download_path.u8string(), + ec.message()); + } + + static fs::path fetch_tool(const VcpkgPaths& paths, const std::string& tool_name, const ToolData& tool_data) + { + const std::array<int, 3>& version = tool_data.version; + const std::string version_as_string = Strings::format("%d.%d.%d", version[0], version[1], version[2]); + Checks::check_exit(VCPKG_LINE_INFO, + !tool_data.url.empty(), + "A suitable version of %s was not found (required v%s) and unable to automatically " + "download a portable one. Please install a newer version of git.", + tool_name, + version_as_string); + System::println("A suitable version of %s was not found (required v%s). Downloading portable %s v%s...", + tool_name, + version_as_string, + tool_name, + version_as_string); + auto& fs = paths.get_filesystem(); + if (!fs.exists(tool_data.download_path)) + { + System::println("Downloading %s...", tool_name); + download_file(fs, tool_data.url, tool_data.download_path, tool_data.sha512); + System::println("Downloading %s... done.", tool_name); + } + else + { + verify_hash(fs, tool_data.url, tool_data.download_path, tool_data.sha512); + } + + if (tool_data.is_archive) + { + System::println("Extracting %s...", tool_name); + extract_archive(paths, tool_data.download_path, tool_data.tool_dir_path); + System::println("Extracting %s... done.", tool_name); + } + else + { + std::error_code ec; + fs.create_directories(tool_data.exe_path.parent_path(), ec); + fs.rename(tool_data.download_path, tool_data.exe_path, ec); + } + + Checks::check_exit(VCPKG_LINE_INFO, + fs.exists(tool_data.exe_path), + "Expected %s to exist after fetching", + tool_data.exe_path.u8string()); + + return tool_data.exe_path; + } + + static fs::path get_cmake_path(const VcpkgPaths& paths) + { + std::vector<fs::path> candidate_paths; +#if defined(_WIN32) || defined(__APPLE__) || defined(__linux__) + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "cmake"); + candidate_paths.push_back(TOOL_DATA.exe_path); +#else + static const ToolData TOOL_DATA = ToolData{{3, 5, 1}, ""}; +#endif + static const std::string VERSION_CHECK_ARGUMENTS = "--version"; + + const std::vector<fs::path> from_path = Files::find_from_PATH("cmake"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "CMake" / "bin" / "cmake.exe"); + + const Optional<fs::path> path = find_if_has_equal_or_greater_version( + paths.get_filesystem(), candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "cmake", TOOL_DATA); + } + + static fs::path get_7za_path(const VcpkgPaths& paths) + { +#if defined(_WIN32) + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "7zip"); + if (!paths.get_filesystem().exists(TOOL_DATA.exe_path)) + { + return fetch_tool(paths, "7zip", TOOL_DATA); + } + return TOOL_DATA.exe_path; +#else + Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot download 7zip for non-Windows platforms."); +#endif + } + + static fs::path get_ninja_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "ninja"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + const std::vector<fs::path> from_path = Files::find_from_PATH("ninja"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + auto path = find_if_has_equal_or_greater_version( + paths.get_filesystem(), candidate_paths, "--version", TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "ninja", TOOL_DATA); + } + + static fs::path get_nuget_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "nuget"); + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + const std::vector<fs::path> from_path = Files::find_from_PATH("nuget"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + auto path = + find_if_has_equal_or_greater_version(paths.get_filesystem(), candidate_paths, "", TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "nuget", TOOL_DATA); + } + + static fs::path get_git_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "git"); + static const std::string VERSION_CHECK_ARGUMENTS = "--version"; + + std::vector<fs::path> candidate_paths; +#if defined(_WIN32) + candidate_paths.push_back(TOOL_DATA.exe_path); +#endif + const std::vector<fs::path> from_path = Files::find_from_PATH("git"); + candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + + const auto& program_files = System::get_program_files_platform_bitness(); + if (const auto pf = program_files.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); + const auto& program_files_32_bit = System::get_program_files_32_bit(); + if (const auto pf = program_files_32_bit.get()) candidate_paths.push_back(*pf / "git" / "cmd" / "git.exe"); + + const Optional<fs::path> path = find_if_has_equal_or_greater_version( + paths.get_filesystem(), candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "git", TOOL_DATA); + } + + static fs::path get_ifw_installerbase_path(const VcpkgPaths& paths) + { + static const ToolData TOOL_DATA = parse_tool_data_from_xml(paths, "installerbase"); + + static const std::string VERSION_CHECK_ARGUMENTS = "--framework-version"; + + std::vector<fs::path> candidate_paths; + candidate_paths.push_back(TOOL_DATA.exe_path); + // TODO: Uncomment later + // const std::vector<fs::path> from_path = Files::find_from_PATH("installerbase"); + // candidate_paths.insert(candidate_paths.end(), from_path.cbegin(), from_path.cend()); + // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / + // "Tools" / "QtInstallerFramework" / "3.1" / "bin" / "installerbase.exe"); + // candidate_paths.push_back(fs::path(System::get_environment_variable("HOMEDRIVE").value_or("C:")) / "Qt" / + // "QtIFW-3.1.0" / "bin" / "installerbase.exe"); + + const Optional<fs::path> path = find_if_has_equal_or_greater_version( + paths.get_filesystem(), candidate_paths, VERSION_CHECK_ARGUMENTS, TOOL_DATA.version); + if (const auto p = path.get()) + { + return *p; + } + + return fetch_tool(paths, "installerbase", TOOL_DATA); + } + + struct VisualStudioInstance + { + enum class ReleaseType + { + STABLE, + PRERELEASE, + LEGACY + }; + + static bool preferred_first_comparator(const VisualStudioInstance& left, const VisualStudioInstance& right) + { + const auto get_preference_weight = [](const ReleaseType& type) -> int { + switch (type) + { + case ReleaseType::STABLE: return 3; + case ReleaseType::PRERELEASE: return 2; + case ReleaseType::LEGACY: return 1; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + }; + + if (left.release_type != right.release_type) + { + return get_preference_weight(left.release_type) > get_preference_weight(right.release_type); + } + + return left.version > right.version; + } + + VisualStudioInstance(fs::path&& root_path, std::string&& version, const ReleaseType& release_type) + : root_path(std::move(root_path)), version(std::move(version)), release_type(release_type) + { + } + + fs::path root_path; + std::string version; + ReleaseType release_type; + + std::string major_version() const { return version.substr(0, 2); } + }; + + static std::vector<VisualStudioInstance> get_visual_studio_instances(const VcpkgPaths& paths) + { + const auto& fs = paths.get_filesystem(); + std::vector<VisualStudioInstance> instances; + + const auto& program_files_32_bit = System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO); + + // Instances from vswhere + const fs::path vswhere_exe = program_files_32_bit / "Microsoft Visual Studio" / "Installer" / "vswhere.exe"; + if (fs.exists(vswhere_exe)) + { + const auto code_and_output = System::cmd_execute_and_capture_output( + Strings::format(R"("%s" -prerelease -legacy -products * -format xml)", vswhere_exe.u8string())); + Checks::check_exit(VCPKG_LINE_INFO, + code_and_output.exit_code == 0, + "Running vswhere.exe failed with message:\n%s", + code_and_output.output); + + const auto instance_entries = find_all_enclosed(code_and_output.output, "<instance>", "</instance>"); + for (const VcpkgStringRange& instance : instance_entries) + { + auto maybe_is_prerelease = find_at_most_one_enclosed(instance, "<isPrerelease>", "</isPrerelease>"); + + VisualStudioInstance::ReleaseType release_type = VisualStudioInstance::ReleaseType::LEGACY; + if (const auto p = maybe_is_prerelease.get()) + { + const auto s = p->to_string(); + if (s == "0") + release_type = VisualStudioInstance::ReleaseType::STABLE; + else if (s == "1") + release_type = VisualStudioInstance::ReleaseType::PRERELEASE; + else + Checks::unreachable(VCPKG_LINE_INFO); + } + + instances.emplace_back( + find_exactly_one_enclosed(instance, "<installationPath>", "</installationPath>").to_string(), + find_exactly_one_enclosed(instance, "<installationVersion>", "</installationVersion>").to_string(), + release_type); + } + } + + const auto append_if_has_cl = [&](fs::path&& path_root) { + const auto cl_exe = path_root / "VC" / "bin" / "cl.exe"; + const auto vcvarsall_bat = path_root / "VC" / "vcvarsall.bat"; + + if (fs.exists(cl_exe) && fs.exists(vcvarsall_bat)) + instances.emplace_back(std::move(path_root), "14.0", VisualStudioInstance::ReleaseType::LEGACY); + }; + + // VS2015 instance from environment variable + auto maybe_vs140_comntools = System::get_environment_variable("vs140comntools"); + if (const auto path_as_string = maybe_vs140_comntools.get()) + { + // We want lexically_normal(), but it is not available + // Correct root path might be 2 or 3 levels up, depending on if the path has trailing backslash. Try both. + auto common7_tools = fs::path{*path_as_string}; + append_if_has_cl(fs::path{*path_as_string}.parent_path().parent_path()); + append_if_has_cl(fs::path{*path_as_string}.parent_path().parent_path().parent_path()); + } + + // VS2015 instance from Program Files + append_if_has_cl(program_files_32_bit / "Microsoft Visual Studio 14.0"); + + return instances; + } + +#if defined(_WIN32) + std::vector<Toolset> find_toolset_instances_preferred_first(const VcpkgPaths& paths) + { + using CPU = System::CPUArchitecture; + + const auto& fs = paths.get_filesystem(); + + // Note: this will contain a mix of vcvarsall.bat locations and dumpbin.exe locations. + std::vector<fs::path> paths_examined; + + std::vector<Toolset> found_toolsets; + std::vector<Toolset> excluded_toolsets; + + const SortedVector<VisualStudioInstance> sorted{get_visual_studio_instances(paths), + VisualStudioInstance::preferred_first_comparator}; + + const bool v140_is_available = Util::find_if(sorted, [&](const VisualStudioInstance& vs_instance) { + return vs_instance.major_version() == "14"; + }) != sorted.end(); + + for (const VisualStudioInstance& vs_instance : sorted) + { + const std::string major_version = vs_instance.major_version(); + if (major_version == "15") + { + const fs::path vc_dir = vs_instance.root_path / "VC"; + + // Skip any instances that do not have vcvarsall. + const fs::path vcvarsall_dir = vc_dir / "Auxiliary" / "Build"; + const fs::path vcvarsall_bat = vcvarsall_dir / "vcvarsall.bat"; + paths_examined.push_back(vcvarsall_bat); + if (!fs.exists(vcvarsall_bat)) continue; + + // Get all supported architectures + std::vector<ToolsetArchOption> supported_architectures; + if (fs.exists(vcvarsall_dir / "vcvars32.bat")) + supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); + if (fs.exists(vcvarsall_dir / "vcvars64.bat")) + supported_architectures.push_back({"amd64", CPU::X64, CPU::X64}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_amd64.bat")) + supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_arm.bat")) + supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); + if (fs.exists(vcvarsall_dir / "vcvarsx86_arm64.bat")) + supported_architectures.push_back({"x86_arm64", CPU::X86, CPU::ARM64}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_x86.bat")) + supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm.bat")) + supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); + if (fs.exists(vcvarsall_dir / "vcvarsamd64_arm64.bat")) + supported_architectures.push_back({"amd64_arm64", CPU::X64, CPU::ARM64}); + + // Locate the "best" MSVC toolchain version + const fs::path msvc_path = vc_dir / "Tools" / "MSVC"; + std::vector<fs::path> msvc_subdirectories = fs.get_files_non_recursive(msvc_path); + Util::unstable_keep_if(msvc_subdirectories, + [&fs](const fs::path& path) { return fs.is_directory(path); }); + + // Sort them so that latest comes first + std::sort( + msvc_subdirectories.begin(), + msvc_subdirectories.end(), + [](const fs::path& left, const fs::path& right) { return left.filename() > right.filename(); }); + + for (const fs::path& subdir : msvc_subdirectories) + { + const fs::path dumpbin_path = subdir / "bin" / "HostX86" / "x86" / "dumpbin.exe"; + paths_examined.push_back(dumpbin_path); + if (fs.exists(dumpbin_path)) + { + const Toolset v141toolset{ + vs_instance.root_path, dumpbin_path, vcvarsall_bat, {}, V_141, supported_architectures}; + + auto english_language_pack = dumpbin_path.parent_path() / "1033"; + + if (!fs.exists(english_language_pack)) + { + excluded_toolsets.push_back(v141toolset); + break; + } + + found_toolsets.push_back(v141toolset); + + if (v140_is_available) + { + const Toolset v140toolset{vs_instance.root_path, + dumpbin_path, + vcvarsall_bat, + {"-vcvars_ver=14.0"}, + V_140, + supported_architectures}; + found_toolsets.push_back(v140toolset); + } + + break; + } + } + + continue; + } + + if (major_version == "14" || major_version == "12") + { + const fs::path vcvarsall_bat = vs_instance.root_path / "VC" / "vcvarsall.bat"; + + paths_examined.push_back(vcvarsall_bat); + if (fs.exists(vcvarsall_bat)) + { + const fs::path vs_dumpbin_exe = vs_instance.root_path / "VC" / "bin" / "dumpbin.exe"; + paths_examined.push_back(vs_dumpbin_exe); + + const fs::path vs_bin_dir = vcvarsall_bat.parent_path() / "bin"; + std::vector<ToolsetArchOption> supported_architectures; + if (fs.exists(vs_bin_dir / "vcvars32.bat")) + supported_architectures.push_back({"x86", CPU::X86, CPU::X86}); + if (fs.exists(vs_bin_dir / "amd64\\vcvars64.bat")) + supported_architectures.push_back({"x64", CPU::X64, CPU::X64}); + if (fs.exists(vs_bin_dir / "x86_amd64\\vcvarsx86_amd64.bat")) + supported_architectures.push_back({"x86_amd64", CPU::X86, CPU::X64}); + if (fs.exists(vs_bin_dir / "x86_arm\\vcvarsx86_arm.bat")) + supported_architectures.push_back({"x86_arm", CPU::X86, CPU::ARM}); + if (fs.exists(vs_bin_dir / "amd64_x86\\vcvarsamd64_x86.bat")) + supported_architectures.push_back({"amd64_x86", CPU::X64, CPU::X86}); + if (fs.exists(vs_bin_dir / "amd64_arm\\vcvarsamd64_arm.bat")) + supported_architectures.push_back({"amd64_arm", CPU::X64, CPU::ARM}); + + if (fs.exists(vs_dumpbin_exe)) + { + const Toolset toolset = {vs_instance.root_path, + vs_dumpbin_exe, + vcvarsall_bat, + {}, + major_version == "14" ? V_140 : V_120, + supported_architectures}; + + auto english_language_pack = vs_dumpbin_exe.parent_path() / "1033"; + + if (!fs.exists(english_language_pack)) + { + excluded_toolsets.push_back(toolset); + break; + } + + found_toolsets.push_back(toolset); + } + } + } + } + + if (!excluded_toolsets.empty()) + { + System::println( + System::Color::warning, + "Warning: The following VS instances are excluded because the English language pack is unavailable."); + for (const Toolset& toolset : excluded_toolsets) + { + System::println(" %s", toolset.visual_studio_root_path.u8string()); + } + System::println(System::Color::warning, "Please install the English language pack."); + } + + if (found_toolsets.empty()) + { + System::println(System::Color::error, "Could not locate a complete toolset."); + System::println("The following paths were examined:"); + for (const fs::path& path : paths_examined) + { + System::println(" %s", path.u8string()); + } + Checks::exit_fail(VCPKG_LINE_INFO); + } + + return found_toolsets; + } +#endif + + fs::path get_tool_path(const VcpkgPaths& paths, const std::string& tool) + { + // First deal with specially handled tools. + // For these we may look in locations like Program Files, the PATH etc as well as the auto-downloaded location. + if (tool == Tools::SEVEN_ZIP) return get_7za_path(paths); + if (tool == Tools::CMAKE) return get_cmake_path(paths); + if (tool == Tools::GIT) return get_git_path(paths); + if (tool == Tools::NINJA) return get_ninja_path(paths); + if (tool == Tools::NUGET) return get_nuget_path(paths); + if (tool == Tools::IFW_INSTALLER_BASE) return get_ifw_installerbase_path(paths); + if (tool == Tools::IFW_BINARYCREATOR) + return get_ifw_installerbase_path(paths).parent_path() / "binarycreator.exe"; + if (tool == Tools::IFW_REPOGEN) return get_ifw_installerbase_path(paths).parent_path() / "repogen.exe"; + + // For other tools, we simply always auto-download them. + const ToolData tool_data = parse_tool_data_from_xml(paths, tool); + if (paths.get_filesystem().exists(tool_data.exe_path)) + { + return tool_data.exe_path; + } + return fetch_tool(paths, tool, tool_data); + } + + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be tool name\n%s", Help::create_example_string("fetch cmake")), + 1, + 1, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + const std::string tool = args.command_arguments[0]; + const fs::path tool_path = get_tool_path(paths, tool); + System::println(tool_path.u8string()); + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.hash.cpp b/toolsrc/src/vcpkg/commands.hash.cpp new file mode 100644 index 000000000..0c7aa72c4 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.hash.cpp @@ -0,0 +1,244 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> + +namespace vcpkg::Commands::Hash +{ + static void verify_has_only_allowed_chars(const std::string& s) + { + static const std::regex ALLOWED_CHARS{"^[a-zA-Z0-9-]*$"}; + Checks::check_exit(VCPKG_LINE_INFO, + std::regex_match(s, ALLOWED_CHARS), + "Only alphanumeric chars and dashes are currently allowed. String was:\n" + " % s", + s); + } +} + +#if defined(_WIN32) +#include <bcrypt.h> + +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif + +namespace vcpkg::Commands::Hash +{ + namespace + { + std::string to_hex(const unsigned char* string, const size_t bytes) + { + static constexpr char HEX_MAP[] = "0123456789abcdef"; + + std::string output; + output.resize(2 * bytes); + + size_t current_char = 0; + for (size_t i = 0; i < bytes; i++) + { + // high + output[current_char] = HEX_MAP[(string[i] & 0xF0) >> 4]; + ++current_char; + // low + output[current_char] = HEX_MAP[(string[i] & 0x0F)]; + ++current_char; + } + + return output; + } + + class BCryptHasher + { + struct BCryptAlgorithmHandle : Util::ResourceBase + { + BCRYPT_ALG_HANDLE handle = nullptr; + + ~BCryptAlgorithmHandle() + { + if (handle) BCryptCloseAlgorithmProvider(handle, 0); + } + }; + + struct BCryptHashHandle : Util::ResourceBase + { + BCRYPT_HASH_HANDLE handle = nullptr; + + ~BCryptHashHandle() + { + if (handle) BCryptDestroyHash(handle); + } + }; + + static void initialize_hash_handle(BCryptHashHandle& hash_handle, + const BCryptAlgorithmHandle& algorithm_handle) + { + const NTSTATUS error_code = + BCryptCreateHash(algorithm_handle.handle, &hash_handle.handle, nullptr, 0, nullptr, 0, 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to initialize the hasher"); + } + + static void hash_data(BCryptHashHandle& hash_handle, const unsigned char* buffer, const size_t& data_size) + { + const NTSTATUS error_code = BCryptHashData( + hash_handle.handle, const_cast<unsigned char*>(buffer), static_cast<ULONG>(data_size), 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to hash data"); + } + + static std::string finalize_hash_handle(const BCryptHashHandle& hash_handle, const ULONG length_in_bytes) + { + std::unique_ptr<unsigned char[]> hash_buffer = std::make_unique<UCHAR[]>(length_in_bytes); + const NTSTATUS error_code = BCryptFinishHash(hash_handle.handle, hash_buffer.get(), length_in_bytes, 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to finalize the hash"); + return to_hex(hash_buffer.get(), length_in_bytes); + } + + public: + explicit BCryptHasher(const std::string& hash_type) + { + NTSTATUS error_code = + BCryptOpenAlgorithmProvider(&this->algorithm_handle.handle, + Strings::to_utf16(Strings::ascii_to_uppercase(hash_type)).c_str(), + nullptr, + 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to open the algorithm provider"); + + DWORD hash_buffer_bytes; + DWORD cb_data; + error_code = BCryptGetProperty(this->algorithm_handle.handle, + BCRYPT_HASH_LENGTH, + reinterpret_cast<PUCHAR>(&hash_buffer_bytes), + sizeof(DWORD), + &cb_data, + 0); + Checks::check_exit(VCPKG_LINE_INFO, NT_SUCCESS(error_code), "Failed to get hash length"); + this->length_in_bytes = hash_buffer_bytes; + } + + std::string hash_file(const fs::path& path) const + { + BCryptHashHandle hash_handle; + initialize_hash_handle(hash_handle, this->algorithm_handle); + + FILE* file = nullptr; + const auto ec = _wfopen_s(&file, path.c_str(), L"rb"); + Checks::check_exit(VCPKG_LINE_INFO, ec == 0, "Failed to open file: %s", path.u8string()); + if (file != nullptr) + { + unsigned char buffer[4096]; + while (const auto actual_size = fread(buffer, 1, sizeof(buffer), file)) + { + hash_data(hash_handle, buffer, actual_size); + } + fclose(file); + } + + return finalize_hash_handle(hash_handle, length_in_bytes); + } + + std::string hash_string(const std::string& s) const + { + BCryptHashHandle hash_handle; + initialize_hash_handle(hash_handle, this->algorithm_handle); + hash_data(hash_handle, reinterpret_cast<const unsigned char*>(s.c_str()), s.size()); + return finalize_hash_handle(hash_handle, length_in_bytes); + } + + private: + BCryptAlgorithmHandle algorithm_handle; + ULONG length_in_bytes; + }; + } + + std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type) + { + Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string()); + return BCryptHasher{hash_type}.hash_file(path); + } + + std::string get_string_hash(const std::string& s, const std::string& hash_type) + { + verify_has_only_allowed_chars(s); + return BCryptHasher{hash_type}.hash_string(s); + } +} + +#else +namespace vcpkg::Commands::Hash +{ + static std::string get_digest_size(const std::string& hash_type) + { + if (!Strings::case_insensitive_ascii_starts_with(hash_type, "SHA")) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "shasum only supports SHA hashes, but %s was provided", hash_type); + } + + return hash_type.substr(3, hash_type.length() - 3); + } + + static std::string run_shasum_and_post_process(const std::string& cmd_line) + { + const auto ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, + ec_data.exit_code == 0, + "Failed to run:\n" + " %s", + cmd_line); + + std::vector<std::string> split = Strings::split(ec_data.output, " "); + Checks::check_exit(VCPKG_LINE_INFO, + split.size() == 3, + "Expected output of the form [hash filename\n] (3 tokens), but got\n" + "[%s] (%s tokens)", + ec_data.output, + std::to_string(split.size())); + + return split[0]; + } + + std::string get_file_hash(const Files::Filesystem& fs, const fs::path& path, const std::string& hash_type) + { + const std::string digest_size = get_digest_size(hash_type); + Checks::check_exit(VCPKG_LINE_INFO, fs.exists(path), "File %s does not exist", path.u8string()); + const std::string cmd_line = Strings::format(R"(shasum -a %s "%s")", digest_size, path.u8string()); + return run_shasum_and_post_process(cmd_line); + } + + std::string get_string_hash(const std::string& s, const std::string& hash_type) + { + const std::string digest_size = get_digest_size(hash_type); + verify_has_only_allowed_chars(s); + + const std::string cmd_line = Strings::format(R"(echo -n "%s" | shasum -a %s)", s, digest_size); + return run_shasum_and_post_process(cmd_line); + } +} +#endif + +namespace vcpkg::Commands::Hash +{ + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be a file path\n%s", + Help::create_example_string("hash boost_1_62_0.tar.bz2")), + 1, + 2, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + const fs::path file_to_hash = args.command_arguments[0]; + const std::string algorithm = args.command_arguments.size() == 2 ? args.command_arguments[1] : "SHA512"; + const std::string hash = get_file_hash(paths.get_filesystem(), file_to_hash, algorithm); + System::println(hash); + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/commands_import.cpp b/toolsrc/src/vcpkg/commands.import.cpp index 5db1885b2..4b595697a 100644 --- a/toolsrc/src/commands_import.cpp +++ b/toolsrc/src/vcpkg/commands.import.cpp @@ -1,9 +1,10 @@ #include "pch.h" -#include "Paragraphs.h" -#include "StatusParagraph.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Files.h" +#include <vcpkg/base/files.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/statusparagraph.h> namespace vcpkg::Commands::Import { @@ -28,7 +29,7 @@ namespace vcpkg::Commands::Import for (auto&& file : files) { if (fs.is_directory(file)) continue; - auto ext = file.extension(); + const auto ext = file.extension(); if (ext == ".dll") binaries.dlls.push_back(std::move(file)); else if (ext == ".lib") @@ -46,7 +47,7 @@ namespace vcpkg::Commands::Import for (auto const& src_path : files) { - fs::path dest_path = destination_folder / src_path.filename(); + const fs::path dest_path = destination_folder / src_path.filename(); fs.copy(src_path, dest_path, fs::copy_options::overwrite_existing); } } @@ -59,10 +60,10 @@ namespace vcpkg::Commands::Import check_is_directory(VCPKG_LINE_INFO, fs, include_directory); check_is_directory(VCPKG_LINE_INFO, fs, project_directory); check_is_directory(VCPKG_LINE_INFO, fs, destination_path); - Binaries debug_binaries = find_binaries_in_dir(fs, project_directory / "Debug"); - Binaries release_binaries = find_binaries_in_dir(fs, project_directory / "Release"); + const Binaries debug_binaries = find_binaries_in_dir(fs, project_directory / "Debug"); + const Binaries release_binaries = find_binaries_in_dir(fs, project_directory / "Release"); - fs::path destination_include_directory = destination_path / "include"; + const fs::path destination_include_directory = destination_path / "include"; fs.copy(include_directory, destination_include_directory, fs::copy_options::recursive | fs::copy_options::overwrite_existing); @@ -82,21 +83,27 @@ namespace vcpkg::Commands::Import const BinaryParagraph& control_file_data) { auto& fs = paths.get_filesystem(); - fs::path library_destination_path = paths.package_dir(control_file_data.spec); + const fs::path library_destination_path = paths.package_dir(control_file_data.spec); std::error_code ec; fs.create_directory(library_destination_path, ec); place_library_files_in(paths.get_filesystem(), include_directory, project_directory, library_destination_path); - fs::path control_file_path = library_destination_path / "CONTROL"; + const fs::path control_file_path = library_destination_path / "CONTROL"; fs.write_contents(control_file_path, Strings::serialize(control_file_data)); } + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string( + R"(import C:\path\to\CONTROLfile C:\path\to\includedir C:\path\to\projectdir)"), + 3, + 3, + {}, + nullptr, + }; + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const std::string example = Commands::Help::create_example_string( - R"(import C:\path\to\CONTROLfile C:\path\to\includedir C:\path\to\projectdir)"); - args.check_exact_arg_count(3, example); - args.check_and_get_optional_command_arguments({}); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); const fs::path control_file_path(args.command_arguments[0]); const fs::path include_directory(args.command_arguments[1]); diff --git a/toolsrc/src/commands_integrate.cpp b/toolsrc/src/vcpkg/commands.integrate.cpp index c5942f9fc..82172e363 100644 --- a/toolsrc/src/commands_integrate.cpp +++ b/toolsrc/src/vcpkg/commands.integrate.cpp @@ -1,19 +1,16 @@ #include "pch.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Files.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/metrics.h> +#include <vcpkg/userconfig.h> namespace vcpkg::Commands::Integrate { - static const std::array<fs::path, 2> old_system_target_files = { - System::get_ProgramFiles_32_bit() / "MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.nuget.targets", - System::get_ProgramFiles_32_bit() / "MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.system.targets"}; - static const fs::path system_wide_targets_file = - System::get_ProgramFiles_32_bit() / "MSBuild/Microsoft.Cpp/v4.0/V140/ImportBefore/Default/vcpkg.system.props"; - +#if defined(_WIN32) static std::string create_appdata_targets_shortcut(const std::string& target_path) noexcept { return Strings::format(R"###( @@ -24,7 +21,9 @@ namespace vcpkg::Commands::Integrate target_path, target_path); } +#endif +#if defined(_WIN32) static std::string create_system_targets_shortcut() noexcept { return R"###( @@ -37,7 +36,9 @@ namespace vcpkg::Commands::Integrate </Project> )###"; } +#endif +#if defined(_WIN32) static std::string create_nuget_targets_file_contents(const fs::path& msbuild_vcpkg_targets_file) noexcept { const std::string as_string = msbuild_vcpkg_targets_file.string(); @@ -53,7 +54,9 @@ namespace vcpkg::Commands::Integrate as_string, as_string); } +#endif +#if defined(_WIN32) static std::string create_nuget_props_file_contents() noexcept { return R"###( @@ -64,7 +67,9 @@ namespace vcpkg::Commands::Integrate </Project> )###"; } +#endif +#if defined(_WIN32) static std::string get_nuget_id(const fs::path& vcpkg_root_dir) { std::string dir_id = vcpkg_root_dir.generic_string(); @@ -77,12 +82,14 @@ namespace vcpkg::Commands::Integrate const std::string nuget_id = "vcpkg." + dir_id; return nuget_id; } +#endif +#if defined(_WIN32) static std::string create_nuspec_file_contents(const fs::path& vcpkg_root_dir, const std::string& nuget_id, const std::string& nupkg_version) { - static constexpr auto content_template = R"( + static constexpr auto CONTENT_TEMPLATE = R"( <package> <metadata> <id>@NUGET_ID@</id> @@ -99,12 +106,14 @@ namespace vcpkg::Commands::Integrate </package> )"; - std::string content = std::regex_replace(content_template, std::regex("@NUGET_ID@"), nuget_id); - content = std::regex_replace(content, std::regex("@VCPKG_DIR@"), vcpkg_root_dir.string()); - content = std::regex_replace(content, std::regex("@VERSION@"), nupkg_version); + std::string content = Strings::replace_all(CONTENT_TEMPLATE, "@NUGET_ID@", nuget_id); + content = Strings::replace_all(std::move(content), "@VCPKG_DIR@", vcpkg_root_dir.string()); + content = Strings::replace_all(std::move(content), "@VERSION@", nupkg_version); return content; } +#endif +#if defined(_WIN32) enum class ElevationPromptChoice { YES, @@ -113,50 +122,64 @@ namespace vcpkg::Commands::Integrate static ElevationPromptChoice 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)) + SHELLEXECUTEINFOW sh_ex_info{}; + sh_ex_info.cbSize = sizeof(sh_ex_info); + sh_ex_info.fMask = SEE_MASK_NOCLOSEPROCESS; + sh_ex_info.hwnd = nullptr; + sh_ex_info.lpVerb = L"runas"; + sh_ex_info.lpFile = L"cmd"; // Application to start + + auto wparam = Strings::to_utf16(param); + sh_ex_info.lpParameters = wparam.c_str(); // Additional parameters + sh_ex_info.lpDirectory = nullptr; + sh_ex_info.nShow = SW_HIDE; + sh_ex_info.hInstApp = nullptr; + + if (!ShellExecuteExW(&sh_ex_info)) { return ElevationPromptChoice::NO; } - if (shExInfo.hProcess == nullptr) + if (sh_ex_info.hProcess == nullptr) { return ElevationPromptChoice::NO; } - WaitForSingleObject(shExInfo.hProcess, INFINITE); - CloseHandle(shExInfo.hProcess); + WaitForSingleObject(sh_ex_info.hProcess, INFINITE); + CloseHandle(sh_ex_info.hProcess); return ElevationPromptChoice::YES; } +#endif +#if defined(_WIN32) static fs::path get_appdata_targets_path() { - static const fs::path local_app_data = - fs::path(System::get_environment_variable(L"LOCALAPPDATA").value_or_exit(VCPKG_LINE_INFO)); - return local_app_data / "vcpkg" / "vcpkg.user.targets"; + static const fs::path LOCAL_APP_DATA = + fs::u8path(System::get_environment_variable("LOCALAPPDATA").value_or_exit(VCPKG_LINE_INFO)); + return LOCAL_APP_DATA / "vcpkg" / "vcpkg.user.targets"; } +#endif - static void integrate_install(const VcpkgPaths& paths) + static fs::path get_path_txt_path() { return get_user_dir() / "vcpkg.path.txt"; } + +#if defined(_WIN32) + static void integrate_install_msbuild14(Files::Filesystem& fs, const fs::path& tmp_dir) { - auto& fs = paths.get_filesystem(); + static const std::array<fs::path, 2> OLD_SYSTEM_TARGET_FILES = { + System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO) / + "MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.nuget.targets", + System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO) / + "MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.system.targets"}; + static const fs::path SYSTEM_WIDE_TARGETS_FILE = + System::get_program_files_32_bit().value_or_exit(VCPKG_LINE_INFO) / + "MSBuild/Microsoft.Cpp/v4.0/V140/ImportBefore/Default/vcpkg.system.props"; // TODO: This block of code should eventually be removed - for (auto&& old_system_wide_targets_file : old_system_target_files) + for (auto&& old_system_wide_targets_file : OLD_SYSTEM_TARGET_FILES) { 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()); - ElevationPromptChoice user_choice = elevated_cmd_execute(param); + const ElevationPromptChoice user_choice = elevated_cmd_execute(param); switch (user_choice) { case ElevationPromptChoice::YES: break; @@ -167,22 +190,16 @@ namespace vcpkg::Commands::Integrate } } } - - std::error_code ec; - const fs::path tmp_dir = paths.buildsystems / "tmp"; - fs.create_directory(paths.buildsystems, ec); - fs.create_directory(tmp_dir, ec); - bool should_install_system = true; - const Expected<std::string> system_wide_file_contents = fs.read_contents(system_wide_targets_file); - if (auto contents_data = system_wide_file_contents.get()) + const Expected<std::string> system_wide_file_contents = fs.read_contents(SYSTEM_WIDE_TARGETS_FILE); + static const std::regex RE(R"###(<!-- version (\d+) -->)###"); + if (const auto contents_data = system_wide_file_contents.get()) { - std::regex re(R"###(<!-- version (\d+) -->)###"); std::match_results<std::string::const_iterator> match; - auto found = std::regex_search(*contents_data, match, re); + const auto found = std::regex_search(*contents_data, match, RE); if (found) { - int ver = atoi(match[1].str().c_str()); + const int ver = atoi(match[1].str().c_str()); if (ver >= 1) should_install_system = false; } } @@ -193,10 +210,10 @@ namespace vcpkg::Commands::Integrate fs.write_contents(sys_src_path, create_system_targets_shortcut()); const std::string param = Strings::format(R"(/c mkdir "%s" & copy "%s" "%s" /Y > nul)", - system_wide_targets_file.parent_path().string(), + SYSTEM_WIDE_TARGETS_FILE.parent_path().string(), sys_src_path.string(), - system_wide_targets_file.string()); - ElevationPromptChoice user_choice = elevated_cmd_execute(param); + SYSTEM_WIDE_TARGETS_FILE.string()); + const ElevationPromptChoice user_choice = elevated_cmd_execute(param); switch (user_choice) { case ElevationPromptChoice::YES: break; @@ -207,47 +224,88 @@ namespace vcpkg::Commands::Integrate } Checks::check_exit(VCPKG_LINE_INFO, - fs.exists(system_wide_targets_file), + fs.exists(SYSTEM_WIDE_TARGETS_FILE), "Error: failed to copy targets file to %s", - system_wide_targets_file.string()); + SYSTEM_WIDE_TARGETS_FILE.string()); } + } +#endif + + static void integrate_install(const VcpkgPaths& paths) + { + auto& fs = paths.get_filesystem(); - const fs::path appdata_src_path = tmp_dir / "vcpkg.user.targets"; - fs.write_contents(appdata_src_path, - create_appdata_targets_shortcut(paths.buildsystems_msbuild_targets.string())); - auto appdata_dst_path = get_appdata_targets_path(); +#if defined(_WIN32) + { + std::error_code ec; + const fs::path tmp_dir = paths.buildsystems / "tmp"; + fs.create_directory(paths.buildsystems, ec); + fs.create_directory(tmp_dir, ec); + + integrate_install_msbuild14(fs, tmp_dir); + + const fs::path appdata_src_path = tmp_dir / "vcpkg.user.targets"; + fs.write_contents(appdata_src_path, + create_appdata_targets_shortcut(paths.buildsystems_msbuild_targets.string())); + auto appdata_dst_path = get_appdata_targets_path(); + + const auto rc = fs.copy_file(appdata_src_path, appdata_dst_path, fs::copy_options::overwrite_existing, ec); - auto rc = fs.copy_file(appdata_src_path, appdata_dst_path, fs::copy_options::overwrite_existing, ec); + if (!rc || ec) + { + System::println(System::Color::error, + "Error: Failed to copy file: %s -> %s", + appdata_src_path.string(), + appdata_dst_path.string()); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } +#endif - if (!rc || ec) + const auto pathtxt = get_path_txt_path(); + std::error_code ec; + fs.write_contents(pathtxt, paths.root.generic_u8string(), ec); + if (ec) { - System::println(System::Color::error, - "Error: Failed to copy file: %s -> %s", - appdata_src_path.string(), - appdata_dst_path.string()); + System::println(System::Color::error, "Error: Failed to write file: %s", pathtxt.string()); Checks::exit_fail(VCPKG_LINE_INFO); } + System::println(System::Color::success, "Applied user-wide integration for this vcpkg root."); const fs::path cmake_toolchain = paths.buildsystems / "vcpkg.cmake"; - System::println("\n" - "All MSBuild C++ projects can now #include any installed libraries.\n" - "Linking will be handled automatically.\n" - "Installing new libraries will make them instantly available.\n" - "\n" - "CMake projects should use -DCMAKE_TOOLCHAIN_FILE=%s", - cmake_toolchain.generic_string()); +#if defined(_WIN32) + System::println( + R"( +All MSBuild C++ projects can now #include any installed libraries. +Linking will be handled automatically. +Installing new libraries will make them instantly available. + +CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=%s")", + cmake_toolchain.generic_string()); +#else + System::println( + R"( +CMake projects should use: "-DCMAKE_TOOLCHAIN_FILE=%s")", + cmake_toolchain.generic_string()); +#endif Checks::exit_success(VCPKG_LINE_INFO); } static void integrate_remove(Files::Filesystem& fs) { + std::error_code ec; + bool was_deleted = false; + +#if defined(_WIN32) const fs::path path = get_appdata_targets_path(); - std::error_code ec; - bool was_deleted = fs.remove(path, ec); + was_deleted |= fs.remove(path, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Error: Unable to remove user-wide integration: %s", ec.message()); +#endif - Checks::check_exit(VCPKG_LINE_INFO, !ec, "Error: Unable to remove user-wide integration: %d", ec.message()); + was_deleted |= fs.remove(get_path_txt_path(), ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec, "Error: Unable to remove user-wide integration: %s", ec.message()); if (was_deleted) { @@ -261,11 +319,12 @@ namespace vcpkg::Commands::Integrate Checks::exit_success(VCPKG_LINE_INFO); } +#if defined(WIN32) static void integrate_project(const VcpkgPaths& paths) { auto& fs = paths.get_filesystem(); - const fs::path& nuget_exe = paths.get_nuget_exe(); + const fs::path& nuget_exe = paths.get_tool_exe(Tools::NUGET); const fs::path& buildsystems_dir = paths.buildsystems; const fs::path tmp_dir = buildsystems_dir / "tmp"; @@ -284,10 +343,10 @@ namespace vcpkg::Commands::Integrate fs.write_contents(nuspec_file_path, create_nuspec_file_contents(paths.root, nuget_id, nupkg_version)); // Using all forward slashes for the command line - const std::wstring cmd_line = Strings::wformat(LR"("%s" pack -OutputDirectory "%s" "%s" > nul)", - nuget_exe.native(), - buildsystems_dir.native(), - nuspec_file_path.native()); + const std::string cmd_line = Strings::format(R"("%s" pack -OutputDirectory "%s" "%s" > nul)", + nuget_exe.u8string(), + buildsystems_dir.u8string(), + nuspec_file_path.u8string()); const int exit_code = System::cmd_execute_clean(cmd_line); @@ -297,7 +356,7 @@ namespace vcpkg::Commands::Integrate System::println(System::Color::success, "Created nupkg: %s", nuget_package.string()); auto source_path = buildsystems_dir.u8string(); - source_path = std::regex_replace(source_path, std::regex("`"), "``"); + source_path = Strings::replace_all(std::move(source_path), "`", "``"); System::println(R"( With a project open, go to Tools->NuGet Package Manager->Package Manager Console and paste: @@ -308,33 +367,103 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console Checks::exit_success(VCPKG_LINE_INFO); } +#endif +#if defined(_WIN32) + static void integrate_powershell(const VcpkgPaths& paths) + { + static constexpr StringLiteral TITLE = "PowerShell Tab-Completion"; + const fs::path script_path = paths.scripts / "addPoshVcpkgToPowershellProfile.ps1"; + + // Console font corruption workaround + SetConsoleCP(437); + SetConsoleOutputCP(437); + + const std::string cmd = Strings::format( + R"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' %s}")", script_path.u8string(), ""); + const int rc = System::cmd_execute(cmd); + + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + if (rc) + { + System::println(System::Color::error, + "%s\n" + "Could not run:\n" + " '%s'", + TITLE, + script_path.generic_string()); + + { + auto locked_metrics = Metrics::g_metrics.lock(); + locked_metrics->track_property("error", "powershell script failed"); + locked_metrics->track_property("title", TITLE); + } + } + + Checks::exit_with_code(VCPKG_LINE_INFO, rc); + } +#endif + +#if defined(_WIN32) const char* const INTEGRATE_COMMAND_HELPSTRING = " vcpkg integrate install Make installed packages available user-wide. Requires admin privileges on " "first use\n" " vcpkg integrate remove Remove user-wide integration\n" - " vcpkg integrate project Generate a referencing nuget package for individual VS project use\n"; + " vcpkg integrate project Generate a referencing nuget package for individual VS project use\n" + " vcpkg integrate powershell Enable PowerShell Tab-Completion\n"; +#else + const char* const INTEGRATE_COMMAND_HELPSTRING = + " vcpkg integrate install Make installed packages available user-wide.\n" + " vcpkg integrate remove Remove user-wide integration\n"; +#endif + + namespace Subcommand + { + static const std::string INSTALL = "install"; + static const std::string REMOVE = "remove"; + static const std::string PROJECT = "project"; + static const std::string POWERSHELL = "powershell"; + } + + static std::vector<std::string> valid_arguments(const VcpkgPaths&) + { + return {Subcommand::INSTALL, Subcommand::REMOVE, Subcommand::PROJECT, Subcommand::POWERSHELL}; + } + + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("Commands:\n" + "%s", + INTEGRATE_COMMAND_HELPSTRING), + 1, + 1, + {}, + &valid_arguments, + }; void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const std::string example = Strings::format("Commands:\n" - "%s", - INTEGRATE_COMMAND_HELPSTRING); - args.check_exact_arg_count(1, example); - args.check_and_get_optional_command_arguments({}); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); - if (args.command_arguments[0] == "install") + if (args.command_arguments[0] == Subcommand::INSTALL) { return integrate_install(paths); } - if (args.command_arguments[0] == "remove") + if (args.command_arguments[0] == Subcommand::REMOVE) { return integrate_remove(paths.get_filesystem()); } - if (args.command_arguments[0] == "project") +#if defined(_WIN32) + if (args.command_arguments[0] == Subcommand::PROJECT) { return integrate_project(paths); } + if (args.command_arguments[0] == Subcommand::POWERSHELL) + { + return integrate_powershell(paths); + } +#endif Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown parameter %s for integrate", args.command_arguments[0]); } diff --git a/toolsrc/src/vcpkg/commands.list.cpp b/toolsrc/src/vcpkg/commands.list.cpp new file mode 100644 index 000000000..cadc06ad3 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.list.cpp @@ -0,0 +1,90 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Commands::List +{ + static constexpr StringLiteral OPTION_FULLDESC = + "--x-full-desc"; // TODO: This should find a better home, eventually + + static void do_print(const StatusParagraph& pgh, const bool full_desc) + { + if (full_desc) + { + System::println("%-50s %-16s %s", pgh.package.displayname(), pgh.package.version, pgh.package.description); + } + else + { + System::println("%-50s %-16s %s", + vcpkg::shorten_text(pgh.package.displayname(), 50), + vcpkg::shorten_text(pgh.package.version, 16), + vcpkg::shorten_text(pgh.package.description, 51)); + } + } + + static constexpr std::array<CommandSwitch, 1> LIST_SWITCHES = {{ + {OPTION_FULLDESC, "Do not truncate long text"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Strings::format( + "The argument should be a substring to search for, or no argument to display all installed libraries.\n%s", + Help::create_example_string("list png")), + 0, + 1, + {LIST_SWITCHES, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + const StatusParagraphs status_paragraphs = database_load_check(paths); + auto installed_ipv = get_installed_ports(status_paragraphs); + + if (installed_ipv.empty()) + { + System::println("No packages are installed. Did you mean `search`?"); + Checks::exit_success(VCPKG_LINE_INFO); + } + + auto installed_packages = Util::fmap(installed_ipv, [](const InstalledPackageView& ipv) { return ipv.core; }); + auto installed_features = + Util::fmap_flatten(installed_ipv, [](const InstalledPackageView& ipv) { return ipv.features; }); + installed_packages.insert(installed_packages.end(), installed_features.begin(), installed_features.end()); + + std::sort(installed_packages.begin(), + installed_packages.end(), + [](const StatusParagraph* lhs, const StatusParagraph* rhs) -> bool { + return lhs->package.displayname() < rhs->package.displayname(); + }); + + if (args.command_arguments.empty()) + { + for (const StatusParagraph* status_paragraph : installed_packages) + { + do_print(*status_paragraph, Util::Sets::contains(options.switches, OPTION_FULLDESC)); + } + } + else + { + // At this point there is 1 argument + for (const StatusParagraph* status_paragraph : installed_packages) + { + const std::string displayname = status_paragraph->package.displayname(); + if (!Strings::case_insensitive_ascii_contains(displayname, args.command_arguments[0])) + { + continue; + } + + do_print(*status_paragraph, Util::Sets::contains(options.switches, OPTION_FULLDESC)); + } + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/commands_owns.cpp b/toolsrc/src/vcpkg/commands.owns.cpp index 757292e96..ee9584651 100644 --- a/toolsrc/src/commands_owns.cpp +++ b/toolsrc/src/vcpkg/commands.owns.cpp @@ -1,8 +1,9 @@ #include "pch.h" -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/vcpkglib.h> namespace vcpkg::Commands::Owns { @@ -22,15 +23,20 @@ namespace vcpkg::Commands::Owns } } } + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be a pattern to search for. %s", + Help::create_example_string("owns zlib.dll")), + 1, + 1, + {}, + nullptr, + }; void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const std::string example = Strings::format("The argument should be a pattern to search for. %s", - Commands::Help::create_example_string("owns zlib.dll")); - args.check_exact_arg_count(1, example); - args.check_and_get_optional_command_arguments({}); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); - StatusParagraphs status_db = database_load_check(paths); + const StatusParagraphs status_db = database_load_check(paths); search_file(paths, args.command_arguments[0], status_db); Checks::exit_success(VCPKG_LINE_INFO); } diff --git a/toolsrc/src/commands_portsdiff.cpp b/toolsrc/src/vcpkg/commands.portsdiff.cpp index a614d654e..2d2b4bd5f 100644 --- a/toolsrc/src/commands_portsdiff.cpp +++ b/toolsrc/src/vcpkg/commands.portsdiff.cpp @@ -1,11 +1,13 @@ #include "pch.h" -#include "Paragraphs.h" -#include "SortedVector.h" -#include "SourceParagraph.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Maps.h" -#include "vcpkg_System.h" +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/versiont.h> + +#include <vcpkg/base/sortedvector.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> namespace vcpkg::Commands::PortsDiff { @@ -69,68 +71,71 @@ namespace vcpkg::Commands::PortsDiff for (const std::string& name : ports_to_print) { const VersionT& version = names_and_versions.at(name); - System::println("%-20s %-16s", name, version); + System::println(" - %-14s %-16s", name, version); } } static std::map<std::string, VersionT> read_ports_from_commit(const VcpkgPaths& paths, - const std::wstring& git_commit_id) + const std::string& git_commit_id) { std::error_code ec; auto& fs = paths.get_filesystem(); - const fs::path& git_exe = paths.get_git_exe(); + const fs::path& git_exe = paths.get_tool_exe(Tools::GIT); const fs::path dot_git_dir = paths.root / ".git"; - const std::wstring ports_dir_name_as_string = paths.ports.filename().native(); + const std::string ports_dir_name_as_string = paths.ports.filename().u8string(); const fs::path temp_checkout_path = - paths.root / Strings::wformat(L"%s-%s", ports_dir_name_as_string, git_commit_id); + paths.root / Strings::format("%s-%s", ports_dir_name_as_string, git_commit_id); fs.create_directory(temp_checkout_path, ec); - const std::wstring checkout_this_dir = - Strings::wformat(LR"(.\%s)", ports_dir_name_as_string); // Must be relative to the root of the repository - - const std::wstring cmd = - Strings::wformat(LR"("%s" --git-dir="%s" --work-tree="%s" checkout %s -f -q -- %s %s & "%s" reset >NUL)", - git_exe.native(), - dot_git_dir.native(), - temp_checkout_path.native(), - git_commit_id, - checkout_this_dir, - L".vcpkg-root", - git_exe.native()); + const auto checkout_this_dir = + Strings::format(R"(.\%s)", ports_dir_name_as_string); // Must be relative to the root of the repository + + const std::string cmd = + Strings::format(R"("%s" --git-dir="%s" --work-tree="%s" checkout %s -f -q -- %s %s & "%s" reset >NUL)", + git_exe.u8string(), + dot_git_dir.u8string(), + temp_checkout_path.u8string(), + git_commit_id, + checkout_this_dir, + ".vcpkg-root", + git_exe.u8string()); System::cmd_execute_clean(cmd); - const std::vector<SourceParagraph> source_paragraphs = + const auto all_ports = Paragraphs::load_all_ports(paths.get_filesystem(), temp_checkout_path / ports_dir_name_as_string); - const std::map<std::string, VersionT> names_and_versions = - Paragraphs::extract_port_names_and_versions(source_paragraphs); + std::map<std::string, VersionT> names_and_versions; + for (auto&& port : all_ports) + names_and_versions.emplace(port->core_paragraph->name, port->core_paragraph->version); fs.remove_all(temp_checkout_path, ec); return names_and_versions; } - static void check_commit_exists(const fs::path& git_exe, const std::wstring& git_commit_id) + static void check_commit_exists(const fs::path& git_exe, const std::string& git_commit_id) { static const std::string VALID_COMMIT_OUTPUT = "commit\n"; - const std::wstring cmd = Strings::wformat(LR"("%s" cat-file -t %s)", git_exe.native(), git_commit_id); + const auto cmd = Strings::format(R"("%s" cat-file -t %s)", git_exe.u8string(), git_commit_id); const System::ExitCodeAndOutput output = System::cmd_execute_and_capture_output(cmd); - Checks::check_exit(VCPKG_LINE_INFO, - output.output == VALID_COMMIT_OUTPUT, - "Invalid commit id %s", - Strings::to_utf8(git_commit_id)); + Checks::check_exit( + VCPKG_LINE_INFO, output.output == VALID_COMMIT_OUTPUT, "Invalid commit id %s", git_commit_id); } + const CommandStructure COMMAND_STRUCTURE = { + Strings::format("The argument should be a branch/tag/hash to checkout.\n%s", + Help::create_example_string("portsdiff mybranchname")), + 1, + 2, + {}, + nullptr, + }; + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) { - static const std::string example = - Strings::format("The argument should be a branch/tag/hash to checkout.\n%s", - Commands::Help::create_example_string("portsdiff mybranchname")); - args.check_min_arg_count(1, example); - args.check_max_arg_count(2, example); - args.check_and_get_optional_command_arguments({}); + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); - const fs::path& git_exe = paths.get_git_exe(); + const fs::path& git_exe = paths.get_tool_exe(Tools::GIT); - const std::wstring git_commit_id_for_previous_snapshot = Strings::to_utf16(args.command_arguments.at(0)); - const std::wstring git_commit_id_for_current_snapshot = - args.command_arguments.size() < 2 ? L"HEAD" : Strings::to_utf16(args.command_arguments.at(1)); + const std::string git_commit_id_for_previous_snapshot = args.command_arguments.at(0); + const std::string git_commit_id_for_current_snapshot = + args.command_arguments.size() < 2 ? "HEAD" : args.command_arguments.at(1); check_commit_exists(git_exe, git_commit_id_for_current_snapshot); check_commit_exists(git_exe, git_commit_id_for_previous_snapshot); @@ -141,8 +146,8 @@ namespace vcpkg::Commands::PortsDiff read_ports_from_commit(paths, git_commit_id_for_previous_snapshot); // Already sorted, so set_difference can work on std::vector too - std::vector<std::string> current_ports = Maps::extract_keys(current_names_and_versions); - std::vector<std::string> previous_ports = Maps::extract_keys(previous_names_and_versions); + const std::vector<std::string> current_ports = Util::extract_keys(current_names_and_versions); + const std::vector<std::string> previous_ports = Util::extract_keys(previous_names_and_versions); const SetElementPresence<std::string> setp = SetElementPresence<std::string>::create(current_ports, previous_ports); @@ -150,14 +155,14 @@ namespace vcpkg::Commands::PortsDiff const std::vector<std::string>& added_ports = setp.only_left; if (!added_ports.empty()) { - System::println("\nThe following %d ports were added:\n", added_ports.size()); + System::println("\nThe following %zd ports were added:", added_ports.size()); do_print_name_and_version(added_ports, current_names_and_versions); } const std::vector<std::string>& removed_ports = setp.only_right; if (!removed_ports.empty()) { - System::println("\nThe following %d ports were removed:\n", removed_ports.size()); + System::println("\nThe following %zd ports were removed:", removed_ports.size()); do_print_name_and_version(removed_ports, previous_names_and_versions); } @@ -167,10 +172,10 @@ namespace vcpkg::Commands::PortsDiff if (!updated_ports.empty()) { - System::println("\nThe following %d ports were updated:\n", updated_ports.size()); + System::println("\nThe following %zd ports were updated:", updated_ports.size()); for (const UpdatedPort& p : updated_ports) { - System::println("%-20s %-16s", p.port, p.version_diff.to_string()); + System::println(" - %-14s %-16s", p.port, p.version_diff.to_string()); } } diff --git a/toolsrc/src/vcpkg/commands.search.cpp b/toolsrc/src/vcpkg/commands.search.cpp new file mode 100644 index 000000000..33a642e40 --- /dev/null +++ b/toolsrc/src/vcpkg/commands.search.cpp @@ -0,0 +1,156 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/sourceparagraph.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Commands::Search +{ + static constexpr StringLiteral OPTION_GRAPH = "--graph"; // TODO: This should find a better home, eventually + static constexpr StringLiteral OPTION_FULLDESC = + "--x-full-desc"; // TODO: This should find a better home, eventually + + static std::string replace_dashes_with_underscore(const std::string& input) + { + std::string output = input; + std::replace(output.begin(), output.end(), '-', '_'); + return output; + } + + static std::string create_graph_as_string( + const std::vector<std::unique_ptr<SourceControlFile>>& source_control_files) + { + int empty_node_count = 0; + + std::string s; + s.append("digraph G{ rankdir=LR; edge [minlen=3]; overlap=false;"); + + for (const auto& source_control_file : source_control_files) + { + const SourceParagraph& source_paragraph = *source_control_file->core_paragraph; + if (source_paragraph.depends.empty()) + { + empty_node_count++; + continue; + } + + const std::string name = replace_dashes_with_underscore(source_paragraph.name); + s.append(Strings::format("%s;", name)); + for (const Dependency& d : source_paragraph.depends) + { + const std::string dependency_name = replace_dashes_with_underscore(d.name()); + s.append(Strings::format("%s -> %s;", name, dependency_name)); + } + } + + s.append(Strings::format("empty [label=\"%d singletons...\"]; }", empty_node_count)); + return s; + } + static void do_print(const SourceParagraph& source_paragraph, bool full_desc) + { + if (full_desc) + { + System::println( + "%-20s %-16s %s", source_paragraph.name, source_paragraph.version, source_paragraph.description); + } + else + { + System::println("%-20s %-16s %s", + vcpkg::shorten_text(source_paragraph.name, 20), + vcpkg::shorten_text(source_paragraph.version, 16), + vcpkg::shorten_text(source_paragraph.description, 81)); + } + } + + static void do_print(const std::string& name, const FeatureParagraph& feature_paragraph, bool full_desc) + { + if (full_desc) + { + System::println("%-37s %s", name + "[" + feature_paragraph.name + "]", feature_paragraph.description); + } + else + { + System::println("%-37s %s", + vcpkg::shorten_text(name + "[" + feature_paragraph.name + "]", 37), + vcpkg::shorten_text(feature_paragraph.description, 81)); + } + } + + static constexpr std::array<CommandSwitch, 2> SEARCH_SWITCHES = {{ + {OPTION_GRAPH, "Open editor into the port-specific buildtree subfolder"}, + {OPTION_FULLDESC, "Do not truncate long text"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Strings::format( + "The argument should be a substring to search for, or no argument to display all libraries.\n%s", + Help::create_example_string("search png")), + 0, + 1, + {SEARCH_SWITCHES, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + const bool full_description = Util::Sets::contains(options.switches, OPTION_FULLDESC); + + auto source_paragraphs = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); + + if (Util::Sets::contains(options.switches, OPTION_GRAPH)) + { + const std::string graph_as_string = create_graph_as_string(source_paragraphs); + System::println(graph_as_string); + Checks::exit_success(VCPKG_LINE_INFO); + } + + if (args.command_arguments.empty()) + { + for (const auto& source_control_file : source_paragraphs) + { + do_print(*source_control_file->core_paragraph, full_description); + for (auto&& feature_paragraph : source_control_file->feature_paragraphs) + { + do_print(source_control_file->core_paragraph->name, *feature_paragraph, full_description); + } + } + } + else + { + const auto& icontains = Strings::case_insensitive_ascii_contains; + + // At this point there is 1 argument + auto&& args_zero = args.command_arguments[0]; + for (const auto& source_control_file : source_paragraphs) + { + auto&& sp = *source_control_file->core_paragraph; + + const bool contains_name = icontains(sp.name, args_zero); + if (contains_name || icontains(sp.description, args_zero)) + { + do_print(sp, full_description); + } + + for (auto&& feature_paragraph : source_control_file->feature_paragraphs) + { + if (contains_name || icontains(feature_paragraph->name, args_zero) || + icontains(feature_paragraph->description, args_zero)) + { + do_print(sp.name, *feature_paragraph, full_description); + } + } + } + } + + System::println( + "\nIf your library is not listed, please open an issue at and/or consider making a pull request:\n" + " https://github.com/Microsoft/vcpkg/issues"); + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.upgrade.cpp b/toolsrc/src/vcpkg/commands.upgrade.cpp new file mode 100644 index 000000000..a902ddeaf --- /dev/null +++ b/toolsrc/src/vcpkg/commands.upgrade.cpp @@ -0,0 +1,180 @@ +#include "pch.h" + +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/install.h> +#include <vcpkg/statusparagraphs.h> +#include <vcpkg/update.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Commands::Upgrade +{ + using Install::KeepGoing; + using Install::to_keep_going; + + static constexpr StringLiteral OPTION_NO_DRY_RUN = "--no-dry-run"; + static constexpr StringLiteral OPTION_KEEP_GOING = "--keep-going"; + + static constexpr std::array<CommandSwitch, 2> INSTALL_SWITCHES = {{ + {OPTION_NO_DRY_RUN, "Actually upgrade"}, + {OPTION_KEEP_GOING, "Continue installing packages on failure"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("upgrade --no-dry-run"), + 0, + SIZE_MAX, + {INSTALL_SWITCHES, {}}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + const bool no_dry_run = Util::Sets::contains(options.switches, OPTION_NO_DRY_RUN); + const KeepGoing keep_going = to_keep_going(Util::Sets::contains(options.switches, OPTION_KEEP_GOING)); + + StatusParagraphs status_db = database_load_check(paths); + + Dependencies::PathsPortFileProvider provider(paths); + Dependencies::PackageGraph graph(provider, status_db); + + // input sanitization + const std::vector<PackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { + return Input::check_and_get_package_spec(arg, default_triplet, COMMAND_STRUCTURE.example_text); + }); + + for (auto&& spec : specs) + { + Input::check_triplet(spec.triplet(), paths); + } + + if (specs.empty()) + { + // If no packages specified, upgrade all outdated packages. + auto outdated_packages = Update::find_outdated_packages(provider, status_db); + + if (outdated_packages.empty()) + { + System::println("All installed packages are up-to-date with the local portfiles."); + Checks::exit_success(VCPKG_LINE_INFO); + } + + for (auto&& outdated_package : outdated_packages) + graph.upgrade(outdated_package.spec); + } + else + { + std::vector<PackageSpec> not_installed; + std::vector<PackageSpec> no_portfile; + std::vector<PackageSpec> to_upgrade; + std::vector<PackageSpec> up_to_date; + + for (auto&& spec : specs) + { + auto it = status_db.find_installed(spec); + if (it == status_db.end()) + { + not_installed.push_back(spec); + } + + auto maybe_scf = provider.get_control_file(spec.name()); + if (auto p_scf = maybe_scf.get()) + { + if (it != status_db.end()) + { + if (p_scf->core_paragraph->version != (*it)->package.version) + { + to_upgrade.push_back(spec); + } + else + { + up_to_date.push_back(spec); + } + } + } + else + { + no_portfile.push_back(spec); + } + } + + Util::sort(not_installed); + Util::sort(no_portfile); + Util::sort(up_to_date); + Util::sort(to_upgrade); + + if (!up_to_date.empty()) + { + System::println(System::Color::success, "The following packages are up-to-date:"); + System::println(Strings::join( + "", up_to_date, [](const PackageSpec& spec) { return " " + spec.to_string() + "\n"; })); + } + + if (!not_installed.empty()) + { + System::println(System::Color::error, "The following packages are not installed:"); + System::println(Strings::join( + "", not_installed, [](const PackageSpec& spec) { return " " + spec.to_string() + "\n"; })); + } + + if (!no_portfile.empty()) + { + System::println(System::Color::error, "The following packages do not have a valid portfile:"); + System::println(Strings::join( + "", no_portfile, [](const PackageSpec& spec) { return " " + spec.to_string() + "\n"; })); + } + + Checks::check_exit(VCPKG_LINE_INFO, not_installed.empty() && no_portfile.empty()); + + if (to_upgrade.empty()) Checks::exit_success(VCPKG_LINE_INFO); + + for (auto&& spec : to_upgrade) + graph.upgrade(spec); + } + + auto plan = graph.serialize(); + + Checks::check_exit(VCPKG_LINE_INFO, !plan.empty()); + + const Build::BuildPackageOptions install_plan_options = {Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN}; + + // Set build settings for all install actions + for (auto&& action : plan) + { + if (auto p_install = action.install_action.get()) + { + p_install->build_options = install_plan_options; + } + } + + Dependencies::print_plan(plan, true); + + if (!no_dry_run) + { + System::println(System::Color::warning, + "If you are sure you want to rebuild the above packages, run this command with the " + "--no-dry-run option."); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + const Install::InstallSummary summary = Install::perform(plan, keep_going, paths, status_db); + + System::println("\nTotal elapsed time: %s\n", summary.total_elapsed_time); + + if (keep_going == KeepGoing::YES) + { + summary.print(); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/commands.version.cpp b/toolsrc/src/vcpkg/commands.version.cpp new file mode 100644 index 000000000..2ad91b57d --- /dev/null +++ b/toolsrc/src/vcpkg/commands.version.cpp @@ -0,0 +1,94 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/metrics.h> + +#define STRINGIFY(...) #__VA_ARGS__ +#define MACRO_TO_STRING(X) STRINGIFY(X) + +#if defined(VCPKG_VERSION) +#define VCPKG_VERSION_AS_STRING MACRO_TO_STRING(VCPKG_VERSION) +#else +#define VCPKG_VERSION_AS_STRING "-unknownhash" +#endif + +namespace vcpkg::Commands::Version +{ + const char* base_version() + { + return +#include "../VERSION.txt" + ; + } + + const std::string& version() + { + static const std::string S_VERSION = +#include "../VERSION.txt" + + +std::string(VCPKG_VERSION_AS_STRING) +#ifndef NDEBUG + + std::string("-debug") +#endif + + std::string(Metrics::get_compiled_metrics_enabled() ? "" : "-external"); + return S_VERSION; + } + + static int scan3(const char* input, const char* pattern, int* a, int* b, int* c) + { +#if defined(_WIN32) + return sscanf_s(input, pattern, a, b, c); +#else + return sscanf(input, pattern, a, b, c); +#endif + } + + void warn_if_vcpkg_version_mismatch(const VcpkgPaths& paths) + { + auto version_file = paths.get_filesystem().read_contents(paths.root / "toolsrc" / "VERSION.txt"); + if (const auto version_contents = version_file.get()) + { + int maj1, min1, rev1; + const auto num1 = scan3(version_contents->c_str(), "\"%d.%d.%d\"", &maj1, &min1, &rev1); + + int maj2, min2, rev2; + const auto num2 = scan3(Version::version().c_str(), "%d.%d.%d-", &maj2, &min2, &rev2); + + if (num1 == 3 && num2 == 3) + { + if (maj1 != maj2 || min1 != min2 || rev1 != rev2) + { + System::println(System::Color::warning, + "Warning: Different source is available for vcpkg (%d.%d.%d -> %d.%d.%d). Use " + ".\\bootstrap-vcpkg.bat to update.", + maj2, + min2, + rev2, + maj1, + min1, + rev1); + } + } + } + } + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("version"), + 0, + 0, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args) + { + Util::unused(args.parse_arguments(COMMAND_STRUCTURE)); + + System::println("Vcpkg package management program version %s\n" + "\n" + "See LICENSE.txt for license information.", + version()); + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/dependencies.cpp b/toolsrc/src/vcpkg/dependencies.cpp new file mode 100644 index 000000000..8fb35b0da --- /dev/null +++ b/toolsrc/src/vcpkg/dependencies.cpp @@ -0,0 +1,885 @@ +#include "pch.h" + +#include <vcpkg/base/files.h> +#include <vcpkg/base/graphs.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/util.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/packagespec.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/statusparagraphs.h> +#include <vcpkg/vcpkglib.h> +#include <vcpkg/vcpkgpaths.h> + +namespace vcpkg::Dependencies +{ + struct ClusterInstalled + { + InstalledPackageView ipv; + std::set<PackageSpec> remove_edges; + std::set<std::string> original_features; + }; + + struct ClusterSource + { + const SourceControlFile* scf = nullptr; + std::unordered_map<std::string, std::vector<FeatureSpec>> build_edges; + }; + + /// <summary> + /// Representation of a package and its features in a ClusterGraph. + /// </summary> + struct Cluster : Util::MoveOnlyBase + { + PackageSpec spec; + + Optional<ClusterInstalled> installed; + Optional<ClusterSource> source; + + // Note: this map can contain "special" strings such as "" and "*" + std::unordered_map<std::string, bool> plus; + std::set<std::string> to_install_features; + bool minus = false; + bool transient_uninstalled = true; + RequestType request_type = RequestType::AUTO_SELECTED; + }; + + struct ClusterPtr + { + Cluster* ptr; + + Cluster* operator->() const { return ptr; } + }; + + bool operator==(const ClusterPtr& l, const ClusterPtr& r) { return l.ptr == r.ptr; } +} + +namespace std +{ + template<> + struct hash<vcpkg::Dependencies::ClusterPtr> + { + size_t operator()(const vcpkg::Dependencies::ClusterPtr& value) const + { + return std::hash<vcpkg::PackageSpec>()(value.ptr->spec); + } + }; +} + +namespace vcpkg::Dependencies +{ + struct GraphPlan + { + Graphs::Graph<ClusterPtr> remove_graph; + Graphs::Graph<ClusterPtr> install_graph; + }; + + /// <summary> + /// Directional graph representing a collection of packages with their features connected by their dependencies. + /// </summary> + struct ClusterGraph : Util::MoveOnlyBase + { + explicit ClusterGraph(const PortFileProvider& provider) : m_provider(provider) {} + + /// <summary> + /// Find the cluster associated with spec or if not found, create it from the PortFileProvider. + /// </summary> + /// <param name="spec">Package spec to get the cluster for.</param> + /// <returns>The cluster found or created for spec.</returns> + Cluster& get(const PackageSpec& spec) + { + auto it = m_graph.find(spec); + if (it == m_graph.end()) + { + // Load on-demand from m_provider + auto maybe_scf = m_provider.get_control_file(spec.name()); + auto& clust = m_graph[spec]; + clust.spec = spec; + if (auto p_scf = maybe_scf.get()) + { + clust.source = cluster_from_scf(*p_scf, clust.spec.triplet()); + } + return clust; + } + return it->second; + } + + private: + static ClusterSource cluster_from_scf(const SourceControlFile& scf, Triplet t) + { + ClusterSource ret; + ret.build_edges.emplace("core", filter_dependencies_to_specs(scf.core_paragraph->depends, t)); + + for (const auto& feature : scf.feature_paragraphs) + ret.build_edges.emplace(feature->name, filter_dependencies_to_specs(feature->depends, t)); + + ret.scf = &scf; + return ret; + } + + std::unordered_map<PackageSpec, Cluster> m_graph; + const PortFileProvider& m_provider; + }; + + std::string to_output_string(RequestType request_type, + const CStringView s, + const Build::BuildPackageOptions& options) + { + const char* const from_head = options.use_head_version == Build::UseHeadVersion::YES ? " (from HEAD)" : ""; + + switch (request_type) + { + case RequestType::AUTO_SELECTED: return Strings::format(" * %s%s", s, from_head); + case RequestType::USER_REQUESTED: return Strings::format(" %s%s", s, from_head); + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + std::string to_output_string(RequestType request_type, const CStringView s) + { + switch (request_type) + { + case RequestType::AUTO_SELECTED: return Strings::format(" * %s", s); + case RequestType::USER_REQUESTED: return Strings::format(" %s", s); + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + + InstallPlanAction::InstallPlanAction() noexcept + : plan_type(InstallPlanType::UNKNOWN), request_type(RequestType::UNKNOWN), build_options{} + { + } + + InstallPlanAction::InstallPlanAction(const PackageSpec& spec, + const SourceControlFile& scf, + const std::set<std::string>& features, + const RequestType& request_type, + std::vector<PackageSpec>&& dependencies) + : spec(spec) + , source_control_file(scf) + , plan_type(InstallPlanType::BUILD_AND_INSTALL) + , request_type(request_type) + , build_options{} + , feature_list(features) + , computed_dependencies(std::move(dependencies)) + { + } + + InstallPlanAction::InstallPlanAction(InstalledPackageView&& ipv, + const std::set<std::string>& features, + const RequestType& request_type) + : spec(ipv.spec()) + , installed_package(std::move(ipv)) + , plan_type(InstallPlanType::ALREADY_INSTALLED) + , request_type(request_type) + , build_options{} + , feature_list(features) + , computed_dependencies(installed_package.get()->dependencies()) + { + } + + std::string InstallPlanAction::displayname() const + { + if (this->feature_list.empty()) + { + return this->spec.to_string(); + } + + const std::string features = Strings::join(",", this->feature_list); + return Strings::format("%s[%s]:%s", this->spec.name(), features, this->spec.triplet()); + } + + bool InstallPlanAction::compare_by_name(const InstallPlanAction* left, const InstallPlanAction* right) + { + return left->spec.name() < right->spec.name(); + } + + RemovePlanAction::RemovePlanAction() noexcept + : plan_type(RemovePlanType::UNKNOWN), request_type(RequestType::UNKNOWN) + { + } + + RemovePlanAction::RemovePlanAction(const PackageSpec& spec, + const RemovePlanType& plan_type, + const RequestType& request_type) + : spec(spec), plan_type(plan_type), request_type(request_type) + { + } + + const PackageSpec& AnyAction::spec() const + { + if (const auto p = install_action.get()) + { + return p->spec; + } + + if (const auto p = remove_action.get()) + { + return p->spec; + } + + Checks::exit_with_message(VCPKG_LINE_INFO, "Null action"); + } + + bool ExportPlanAction::compare_by_name(const ExportPlanAction* left, const ExportPlanAction* right) + { + return left->spec.name() < right->spec.name(); + } + + ExportPlanAction::ExportPlanAction() noexcept + : plan_type(ExportPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) + { + } + + ExportPlanAction::ExportPlanAction(const PackageSpec& spec, + InstalledPackageView&& installed_package, + const RequestType& request_type) + : spec(spec) + , plan_type(ExportPlanType::ALREADY_BUILT) + , request_type(request_type) + , m_installed_package(std::move(installed_package)) + { + } + + ExportPlanAction::ExportPlanAction(const PackageSpec& spec, const RequestType& request_type) + : spec(spec), plan_type(ExportPlanType::NOT_BUILT), request_type(request_type) + { + } + + Optional<const BinaryParagraph&> ExportPlanAction::core_paragraph() const + { + if (auto p_ip = m_installed_package.get()) + { + return p_ip->core->package; + } + return nullopt; + } + + std::vector<PackageSpec> ExportPlanAction::dependencies(const Triplet&) const + { + if (auto p_ip = m_installed_package.get()) + return p_ip->dependencies(); + else + return {}; + } + + bool RemovePlanAction::compare_by_name(const RemovePlanAction* left, const RemovePlanAction* right) + { + return left->spec.name() < right->spec.name(); + } + + MapPortFileProvider::MapPortFileProvider(const std::unordered_map<std::string, SourceControlFile>& map) : ports(map) + { + } + + Optional<const SourceControlFile&> MapPortFileProvider::get_control_file(const std::string& spec) const + { + auto scf = ports.find(spec); + if (scf == ports.end()) return nullopt; + return scf->second; + } + + PathsPortFileProvider::PathsPortFileProvider(const VcpkgPaths& paths) : ports(paths) {} + + Optional<const SourceControlFile&> PathsPortFileProvider::get_control_file(const std::string& spec) const + { + auto cache_it = cache.find(spec); + if (cache_it != cache.end()) + { + return cache_it->second; + } + Parse::ParseExpected<SourceControlFile> source_control_file = + Paragraphs::try_load_port(ports.get_filesystem(), ports.port_dir(spec)); + + if (auto scf = source_control_file.get()) + { + auto it = cache.emplace(spec, std::move(*scf->get())); + return it.first->second; + } + return nullopt; + } + + std::vector<RemovePlanAction> create_remove_plan(const std::vector<PackageSpec>& specs, + const StatusParagraphs& status_db) + { + struct RemoveAdjacencyProvider final : Graphs::AdjacencyProvider<PackageSpec, RemovePlanAction> + { + const StatusParagraphs& status_db; + const std::vector<InstalledPackageView>& installed_ports; + const std::unordered_set<PackageSpec>& specs_as_set; + + RemoveAdjacencyProvider(const StatusParagraphs& status_db, + const std::vector<InstalledPackageView>& installed_ports, + const std::unordered_set<PackageSpec>& specs_as_set) + : status_db(status_db), installed_ports(installed_ports), specs_as_set(specs_as_set) + { + } + + std::vector<PackageSpec> adjacency_list(const RemovePlanAction& plan) const override + { + if (plan.plan_type == RemovePlanType::NOT_INSTALLED) + { + return {}; + } + + const PackageSpec& spec = plan.spec; + std::vector<PackageSpec> dependents; + for (auto&& ipv : installed_ports) + { + auto deps = ipv.dependencies(); + + if (std::find(deps.begin(), deps.end(), spec) == deps.end()) continue; + + dependents.push_back(ipv.spec()); + } + + return dependents; + } + + RemovePlanAction load_vertex_data(const PackageSpec& spec) const override + { + const RequestType request_type = specs_as_set.find(spec) != specs_as_set.end() + ? RequestType::USER_REQUESTED + : RequestType::AUTO_SELECTED; + const StatusParagraphs::const_iterator it = status_db.find_installed(spec); + if (it == status_db.end()) + { + return RemovePlanAction{spec, RemovePlanType::NOT_INSTALLED, request_type}; + } + return RemovePlanAction{spec, RemovePlanType::REMOVE, request_type}; + } + + std::string to_string(const PackageSpec& spec) const override { return spec.to_string(); } + }; + + auto installed_ports = get_installed_ports(status_db); + const std::unordered_set<PackageSpec> specs_as_set(specs.cbegin(), specs.cend()); + return Graphs::topological_sort(specs, RemoveAdjacencyProvider{status_db, installed_ports, specs_as_set}); + } + + std::vector<ExportPlanAction> create_export_plan(const std::vector<PackageSpec>& specs, + const StatusParagraphs& status_db) + { + struct ExportAdjacencyProvider final : Graphs::AdjacencyProvider<PackageSpec, ExportPlanAction> + { + const StatusParagraphs& status_db; + const std::unordered_set<PackageSpec>& specs_as_set; + + ExportAdjacencyProvider(const StatusParagraphs& s, const std::unordered_set<PackageSpec>& specs_as_set) + : status_db(s), specs_as_set(specs_as_set) + { + } + + std::vector<PackageSpec> adjacency_list(const ExportPlanAction& plan) const override + { + return plan.dependencies(plan.spec.triplet()); + } + + ExportPlanAction load_vertex_data(const PackageSpec& spec) const override + { + const RequestType request_type = specs_as_set.find(spec) != specs_as_set.end() + ? RequestType::USER_REQUESTED + : RequestType::AUTO_SELECTED; + + auto maybe_ipv = status_db.find_all_installed(spec); + + if (auto p_ipv = maybe_ipv.get()) + { + return ExportPlanAction{spec, std::move(*p_ipv), request_type}; + } + + return ExportPlanAction{spec, request_type}; + } + + std::string to_string(const PackageSpec& spec) const override { return spec.to_string(); } + }; + + const std::unordered_set<PackageSpec> specs_as_set(specs.cbegin(), specs.cend()); + std::vector<ExportPlanAction> toposort = + Graphs::topological_sort(specs, ExportAdjacencyProvider{status_db, specs_as_set}); + return toposort; + } + + enum class MarkPlusResult + { + FEATURE_NOT_FOUND, + SUCCESS, + }; + + static MarkPlusResult mark_plus(const std::string& feature, + Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features); + + static void mark_minus(Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features); + + static MarkPlusResult follow_plus_dependencies(const std::string& feature, + Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features) + { + if (auto p_source = cluster.source.get()) + { + auto it_build_edges = p_source->build_edges.find(feature); + if (it_build_edges != p_source->build_edges.end()) + { + // mark this package for rebuilding if needed + mark_minus(cluster, graph, graph_plan, prevent_default_features); + + graph_plan.install_graph.add_vertex({&cluster}); + cluster.to_install_features.insert(feature); + + if (feature != "core") + { + // All features implicitly depend on core + auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); + + // Should be impossible for "core" to not exist + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); + } + + if (!cluster.installed.get() && !Util::Sets::contains(prevent_default_features, cluster.spec.name())) + { + // Add the default features of this package if it was not previously installed and it isn't being + // suppressed. + auto res = mark_plus("", cluster, graph, graph_plan, prevent_default_features); + + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: Unable to satisfy default dependencies of %s", + cluster.spec); + } + + for (auto&& depend : it_build_edges->second) + { + auto& depend_cluster = graph.get(depend.spec()); + auto res = mark_plus(depend.feature(), depend_cluster, graph, graph_plan, prevent_default_features); + + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: Unable to satisfy dependency %s of %s", + depend, + FeatureSpec(cluster.spec, feature)); + + if (&depend_cluster == &cluster) continue; + graph_plan.install_graph.add_edge({&cluster}, {&depend_cluster}); + } + + return MarkPlusResult::SUCCESS; + } + } + + // The feature was not available in the installed package nor the source paragraph. + return MarkPlusResult::FEATURE_NOT_FOUND; + } + + MarkPlusResult mark_plus(const std::string& feature, + Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features) + { + auto& plus = cluster.plus[feature]; + if (plus) return MarkPlusResult::SUCCESS; + plus = true; + + if (feature.empty()) + { + // Add default features for this package. This is an exact reference, so ignore prevent_default_features. + if (auto p_source = cluster.source.get()) + { + for (auto&& default_feature : p_source->scf->core_paragraph.get()->default_features) + { + auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) + { + return res; + } + } + } + else + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Error: Unable to install default features because can't find CONTROL for %s", + cluster.spec); + } + + // "core" is always required. + return mark_plus("core", cluster, graph, graph_plan, prevent_default_features); + } + + if (feature == "*") + { + if (auto p_source = cluster.source.get()) + { + for (auto&& fpgh : p_source->scf->feature_paragraphs) + { + auto res = mark_plus(fpgh->name, cluster, graph, graph_plan, prevent_default_features); + + Checks::check_exit(VCPKG_LINE_INFO, + res == MarkPlusResult::SUCCESS, + "Error: Unable to locate feature %s in %s", + fpgh->name, + cluster.spec); + } + + auto res = mark_plus("core", cluster, graph, graph_plan, prevent_default_features); + + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS); + } + else + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Error: Unable to handle '*' because can't find CONTROL for %s", cluster.spec); + } + return MarkPlusResult::SUCCESS; + } + + if (auto p_installed = cluster.installed.get()) + { + if (p_installed->original_features.find(feature) != p_installed->original_features.end()) + { + return MarkPlusResult::SUCCESS; + } + } + + // This feature was or will be uninstalled, therefore we need to rebuild + mark_minus(cluster, graph, graph_plan, prevent_default_features); + + return follow_plus_dependencies(feature, cluster, graph, graph_plan, prevent_default_features); + } + + void mark_minus(Cluster& cluster, + ClusterGraph& graph, + GraphPlan& graph_plan, + const std::unordered_set<std::string>& prevent_default_features) + { + if (cluster.minus) return; + cluster.minus = true; + cluster.transient_uninstalled = true; + + auto p_installed = cluster.installed.get(); + auto p_source = cluster.source.get(); + + Checks::check_exit( + VCPKG_LINE_INFO, + p_source, + "Error: cannot locate new portfile for %s. Please explicitly remove this package with `vcpkg remove %s`.", + cluster.spec, + cluster.spec); + + if (p_installed) + { + graph_plan.remove_graph.add_vertex({&cluster}); + for (auto&& edge : p_installed->remove_edges) + { + auto& depend_cluster = graph.get(edge); + Checks::check_exit(VCPKG_LINE_INFO, &cluster != &depend_cluster); + graph_plan.remove_graph.add_edge({&cluster}, {&depend_cluster}); + mark_minus(depend_cluster, graph, graph_plan, prevent_default_features); + } + + // Reinstall all original features. Don't use mark_plus because it will ignore them since they are + // "already installed". + for (auto&& f : p_installed->original_features) + { + auto res = follow_plus_dependencies(f, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) + { + System::println(System::Color::warning, + "Warning: could not reinstall feature %s", + FeatureSpec{cluster.spec, f}); + } + } + + // Check if any default features have been added + auto& previous_df = p_installed->ipv.core->package.default_features; + for (auto&& default_feature : p_source->scf->core_paragraph->default_features) + { + if (std::find(previous_df.begin(), previous_df.end(), default_feature) == previous_df.end()) + { + // This is a new default feature, mark it for installation + auto res = mark_plus(default_feature, cluster, graph, graph_plan, prevent_default_features); + if (res != MarkPlusResult::SUCCESS) + { + System::println(System::Color::warning, + "Warning: could not install new default feature %s", + FeatureSpec{cluster.spec, default_feature}); + } + } + } + } + } + + /// <summary>Figure out which actions are required to install features specifications in `specs`.</summary> + /// <param name="provider">Contains the ports of the current environment.</param> + /// <param name="specs">Feature specifications to resolve dependencies for.</param> + /// <param name="status_db">Status of installed packages in the current environment.</param> + std::vector<AnyAction> create_feature_install_plan(const PortFileProvider& provider, + const std::vector<FeatureSpec>& specs, + const StatusParagraphs& status_db) + { + std::unordered_set<std::string> prevent_default_features; + for (auto&& spec : specs) + { + // When "core" is explicitly listed, default features should not be installed. + if (spec.feature() == "core") prevent_default_features.insert(spec.name()); + } + + PackageGraph pgraph(provider, status_db); + for (auto&& spec : specs) + { + // If preventing default features, ignore the automatically generated "" references + if (spec.feature().empty() && Util::Sets::contains(prevent_default_features, spec.name())) continue; + pgraph.install(spec, prevent_default_features); + } + + return pgraph.serialize(); + } + + /// <summary>Figure out which actions are required to install features specifications in `specs`.</summary> + /// <param name="map">Map of all source files in the current environment.</param> + /// <param name="specs">Feature specifications to resolve dependencies for.</param> + /// <param name="status_db">Status of installed packages in the current environment.</param> + std::vector<AnyAction> create_feature_install_plan(const std::unordered_map<std::string, SourceControlFile>& map, + const std::vector<FeatureSpec>& specs, + const StatusParagraphs& status_db) + { + MapPortFileProvider provider(map); + return create_feature_install_plan(provider, specs, status_db); + } + + /// <param name="prevent_default_features"> + /// List of package names for which default features should not be installed instead of the core package (e.g. if + /// the user is currently installing specific features of that package). + /// </param> + void PackageGraph::install(const FeatureSpec& spec, + const std::unordered_set<std::string>& prevent_default_features) const + { + Cluster& spec_cluster = m_graph->get(spec.spec()); + spec_cluster.request_type = RequestType::USER_REQUESTED; + + auto res = mark_plus(spec.feature(), spec_cluster, *m_graph, *m_graph_plan, prevent_default_features); + + Checks::check_exit(VCPKG_LINE_INFO, res == MarkPlusResult::SUCCESS, "Error: Unable to locate feature %s", spec); + + m_graph_plan->install_graph.add_vertex(ClusterPtr{&spec_cluster}); + } + + void PackageGraph::upgrade(const PackageSpec& spec) const + { + Cluster& spec_cluster = m_graph->get(spec); + spec_cluster.request_type = RequestType::USER_REQUESTED; + + mark_minus(spec_cluster, *m_graph, *m_graph_plan, {}); + } + + std::vector<AnyAction> PackageGraph::serialize() const + { + auto remove_vertex_list = m_graph_plan->remove_graph.vertex_list(); + auto remove_toposort = Graphs::topological_sort(remove_vertex_list, m_graph_plan->remove_graph); + + auto insert_vertex_list = m_graph_plan->install_graph.vertex_list(); + auto insert_toposort = Graphs::topological_sort(insert_vertex_list, m_graph_plan->install_graph); + + std::vector<AnyAction> plan; + + for (auto&& p_cluster : remove_toposort) + { + plan.emplace_back(RemovePlanAction{ + std::move(p_cluster->spec), + RemovePlanType::REMOVE, + p_cluster->request_type, + }); + } + + for (auto&& p_cluster : insert_toposort) + { + if (p_cluster->transient_uninstalled) + { + // If it will be transiently uninstalled, we need to issue a full installation command + auto pscf = p_cluster->source.value_or_exit(VCPKG_LINE_INFO).scf; + + auto dep_specs = Util::fmap(m_graph_plan->install_graph.adjacency_list(p_cluster), + [](ClusterPtr const& p) { return p->spec; }); + Util::sort_unique_erase(dep_specs); + + plan.emplace_back(InstallPlanAction{ + p_cluster->spec, + *pscf, + p_cluster->to_install_features, + p_cluster->request_type, + std::move(dep_specs), + }); + } + else + { + // If the package isn't transitively installed, still include it if the user explicitly requested it + if (p_cluster->request_type != RequestType::USER_REQUESTED) continue; + auto&& installed = p_cluster->installed.value_or_exit(VCPKG_LINE_INFO); + plan.emplace_back(InstallPlanAction{ + InstalledPackageView{installed.ipv}, + installed.original_features, + p_cluster->request_type, + }); + } + } + + return plan; + } + + static std::unique_ptr<ClusterGraph> create_feature_install_graph(const PortFileProvider& map, + const StatusParagraphs& status_db) + { + std::unique_ptr<ClusterGraph> graph = std::make_unique<ClusterGraph>(map); + + auto installed_ports = get_installed_ports(status_db); + + for (auto&& ipv : installed_ports) + { + Cluster& cluster = graph->get(ipv.spec()); + + cluster.transient_uninstalled = false; + + cluster.installed = [](const InstalledPackageView& ipv) -> ClusterInstalled { + ClusterInstalled ret; + ret.ipv = ipv; + ret.original_features.emplace("core"); + for (auto&& feature : ipv.features) + ret.original_features.emplace(feature->package.feature); + return ret; + }(ipv); + } + + // Populate the graph with "remove edges", which are the reverse of the Build-Depends edges. + for (auto&& ipv : installed_ports) + { + auto deps = ipv.dependencies(); + + for (auto&& dep : deps) + { + auto p_installed = graph->get(dep).installed.get(); + Checks::check_exit(VCPKG_LINE_INFO, + p_installed, + "Error: database corrupted. Package %s is installed but dependency %s is not.", + ipv.spec(), + dep); + p_installed->remove_edges.emplace(ipv.spec()); + } + } + return graph; + } + + PackageGraph::PackageGraph(const PortFileProvider& provider, const StatusParagraphs& status_db) + : m_graph_plan(std::make_unique<GraphPlan>()), m_graph(create_feature_install_graph(provider, status_db)) + { + } + + PackageGraph::~PackageGraph() = default; + + void print_plan(const std::vector<AnyAction>& action_plan, const bool is_recursive) + { + std::vector<const RemovePlanAction*> remove_plans; + std::vector<const InstallPlanAction*> rebuilt_plans; + std::vector<const InstallPlanAction*> only_install_plans; + std::vector<const InstallPlanAction*> new_plans; + std::vector<const InstallPlanAction*> already_installed_plans; + std::vector<const InstallPlanAction*> excluded; + + const bool has_non_user_requested_packages = Util::find_if(action_plan, [](const AnyAction& package) -> bool { + if (auto iplan = package.install_action.get()) + return iplan->request_type != RequestType::USER_REQUESTED; + else + return false; + }) != action_plan.cend(); + + for (auto&& action : action_plan) + { + if (auto install_action = action.install_action.get()) + { + // remove plans are guaranteed to come before install plans, so we know the plan will be contained if at + // all. + auto it = Util::find_if( + remove_plans, [&](const RemovePlanAction* plan) { return plan->spec == install_action->spec; }); + if (it != remove_plans.end()) + { + rebuilt_plans.emplace_back(install_action); + } + else + { + switch (install_action->plan_type) + { + case InstallPlanType::ALREADY_INSTALLED: + if (install_action->request_type == RequestType::USER_REQUESTED) + already_installed_plans.emplace_back(install_action); + break; + case InstallPlanType::BUILD_AND_INSTALL: new_plans.emplace_back(install_action); break; + case InstallPlanType::EXCLUDED: excluded.emplace_back(install_action); break; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + } + else if (auto remove_action = action.remove_action.get()) + { + remove_plans.emplace_back(remove_action); + } + } + + std::sort(remove_plans.begin(), remove_plans.end(), &RemovePlanAction::compare_by_name); + std::sort(rebuilt_plans.begin(), rebuilt_plans.end(), &InstallPlanAction::compare_by_name); + std::sort(only_install_plans.begin(), only_install_plans.end(), &InstallPlanAction::compare_by_name); + std::sort(new_plans.begin(), new_plans.end(), &InstallPlanAction::compare_by_name); + std::sort(already_installed_plans.begin(), already_installed_plans.end(), &InstallPlanAction::compare_by_name); + std::sort(excluded.begin(), excluded.end(), &InstallPlanAction::compare_by_name); + + static auto actions_to_output_string = [](const std::vector<const InstallPlanAction*>& v) { + return Strings::join("\n", v, [](const InstallPlanAction* p) { + return to_output_string(p->request_type, p->displayname(), p->build_options); + }); + }; + + if (!excluded.empty()) + { + System::println("The following packages are excluded:\n%s", actions_to_output_string(excluded)); + } + + if (!already_installed_plans.empty()) + { + System::println("The following packages are already installed:\n%s", + actions_to_output_string(already_installed_plans)); + } + + if (!rebuilt_plans.empty()) + { + System::println("The following packages will be rebuilt:\n%s", actions_to_output_string(rebuilt_plans)); + } + + if (!new_plans.empty()) + { + System::println("The following packages will be built and installed:\n%s", + actions_to_output_string(new_plans)); + } + + if (!only_install_plans.empty()) + { + System::println("The following packages will be directly installed:\n%s", + actions_to_output_string(only_install_plans)); + } + + if (has_non_user_requested_packages) + System::println("Additional packages (*) will be modified to complete this operation."); + + if (!remove_plans.empty() && !is_recursive) + { + System::println(System::Color::warning, + "If you are sure you want to rebuild the above packages, run the command with the " + "--recurse option"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } +} diff --git a/toolsrc/src/vcpkg/export.cpp b/toolsrc/src/vcpkg/export.cpp new file mode 100644 index 000000000..c444cf3d0 --- /dev/null +++ b/toolsrc/src/vcpkg/export.cpp @@ -0,0 +1,543 @@ +#include "pch.h" + +#include <vcpkg/base/stringliteral.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/export.h> +#include <vcpkg/export.ifw.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/install.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Export +{ + using Dependencies::ExportPlanAction; + using Dependencies::ExportPlanType; + using Dependencies::RequestType; + using Install::InstallDir; + + static std::string create_nuspec_file_contents(const std::string& raw_exported_dir, + const std::string& targets_redirect_path, + const std::string& nuget_id, + const std::string& nupkg_version) + { + static constexpr auto CONTENT_TEMPLATE = R"( +<package> + <metadata> + <id>@NUGET_ID@</id> + <version>@VERSION@</version> + <authors>vcpkg</authors> + <description> + Vcpkg NuGet export + </description> + </metadata> + <files> + <file src="@RAW_EXPORTED_DIR@\installed\**" target="installed" /> + <file src="@RAW_EXPORTED_DIR@\scripts\**" target="scripts" /> + <file src="@RAW_EXPORTED_DIR@\.vcpkg-root" target="" /> + <file src="@TARGETS_REDIRECT_PATH@" target="build\native\@NUGET_ID@.targets" /> + </files> +</package> +)"; + + std::string nuspec_file_content = Strings::replace_all(CONTENT_TEMPLATE, "@NUGET_ID@", nuget_id); + nuspec_file_content = Strings::replace_all(std::move(nuspec_file_content), "@VERSION@", nupkg_version); + nuspec_file_content = + Strings::replace_all(std::move(nuspec_file_content), "@RAW_EXPORTED_DIR@", raw_exported_dir); + nuspec_file_content = + Strings::replace_all(std::move(nuspec_file_content), "@TARGETS_REDIRECT_PATH@", targets_redirect_path); + return nuspec_file_content; + } + + static std::string create_targets_redirect(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')" Project="%s" /> +</Project> +)###", + target_path, + target_path); + } + + static void print_plan(const std::map<ExportPlanType, std::vector<const ExportPlanAction*>>& group_by_plan_type) + { + static constexpr std::array<ExportPlanType, 2> ORDER = {ExportPlanType::ALREADY_BUILT, + ExportPlanType::NOT_BUILT}; + static constexpr Build::BuildPackageOptions BUILD_OPTIONS = {Build::UseHeadVersion::NO, + Build::AllowDownloads::YES, + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + Build::DownloadTool::BUILT_IN}; + + for (const ExportPlanType plan_type : ORDER) + { + const auto it = group_by_plan_type.find(plan_type); + if (it == group_by_plan_type.cend()) + { + continue; + } + + std::vector<const ExportPlanAction*> cont = it->second; + std::sort(cont.begin(), cont.end(), &ExportPlanAction::compare_by_name); + const std::string as_string = Strings::join("\n", cont, [](const ExportPlanAction* p) { + return Dependencies::to_output_string(p->request_type, p->spec.to_string(), BUILD_OPTIONS); + }); + + switch (plan_type) + { + case ExportPlanType::ALREADY_BUILT: + System::println("The following packages are already built and will be exported:\n%s", as_string); + continue; + case ExportPlanType::NOT_BUILT: + System::println("The following packages need to be built:\n%s", as_string); + continue; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + } + + static std::string create_export_id() + { + const tm date_time = System::get_current_date_time(); + + // Format is: YYYYmmdd-HHMMSS + // 15 characters + 1 null terminating character will be written for a total of 16 chars + char mbstr[16]; + const size_t bytes_written = std::strftime(mbstr, sizeof(mbstr), "%Y%m%d-%H%M%S", &date_time); + Checks::check_exit(VCPKG_LINE_INFO, + bytes_written == 15, + "Expected 15 bytes to be written, but %u were written", + bytes_written); + const std::string date_time_as_string(mbstr); + return ("vcpkg-export-" + date_time_as_string); + } + + static fs::path do_nuget_export(const VcpkgPaths& paths, + const std::string& nuget_id, + const std::string& nuget_version, + const fs::path& raw_exported_dir, + const fs::path& output_dir) + { + Files::Filesystem& fs = paths.get_filesystem(); + const fs::path& nuget_exe = paths.get_tool_exe(Tools::NUGET); + + // This file will be placed in "build\native" in the nuget package. Therefore, go up two dirs. + const std::string targets_redirect_content = + create_targets_redirect("../../scripts/buildsystems/msbuild/vcpkg.targets"); + const fs::path targets_redirect = paths.buildsystems / "tmp" / "vcpkg.export.nuget.targets"; + + std::error_code ec; + fs.create_directories(paths.buildsystems / "tmp", ec); + + fs.write_contents(targets_redirect, targets_redirect_content); + + const std::string nuspec_file_content = + create_nuspec_file_contents(raw_exported_dir.string(), targets_redirect.string(), nuget_id, nuget_version); + const fs::path nuspec_file_path = paths.buildsystems / "tmp" / "vcpkg.export.nuspec"; + fs.write_contents(nuspec_file_path, nuspec_file_content); + + // -NoDefaultExcludes is needed for ".vcpkg-root" + const auto cmd_line = Strings::format(R"("%s" pack -OutputDirectory "%s" "%s" -NoDefaultExcludes > nul)", + nuget_exe.u8string(), + output_dir.u8string(), + nuspec_file_path.u8string()); + + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit(VCPKG_LINE_INFO, exit_code == 0, "Error: NuGet package creation failed"); + + const fs::path output_path = output_dir / (nuget_id + ".nupkg"); + return output_path; + } + + struct ArchiveFormat final + { + enum class BackingEnum + { + ZIP = 1, + SEVEN_ZIP, + }; + + constexpr ArchiveFormat() = delete; + + constexpr ArchiveFormat(BackingEnum backing_enum, const char* extension, const char* cmake_option) + : backing_enum(backing_enum), m_extension(extension), m_cmake_option(cmake_option) + { + } + + constexpr operator BackingEnum() const { return backing_enum; } + constexpr CStringView extension() const { return this->m_extension; } + constexpr CStringView cmake_option() const { return this->m_cmake_option; } + + private: + BackingEnum backing_enum; + const char* m_extension; + const char* m_cmake_option; + }; + + namespace ArchiveFormatC + { + constexpr const ArchiveFormat ZIP(ArchiveFormat::BackingEnum::ZIP, "zip", "zip"); + constexpr const ArchiveFormat SEVEN_ZIP(ArchiveFormat::BackingEnum::SEVEN_ZIP, "7z", "7zip"); + } + + static fs::path do_archive_export(const VcpkgPaths& paths, + const fs::path& raw_exported_dir, + const fs::path& output_dir, + const ArchiveFormat& format) + { + const fs::path& cmake_exe = paths.get_tool_exe(Tools::CMAKE); + + const std::string exported_dir_filename = raw_exported_dir.filename().u8string(); + const std::string exported_archive_filename = + Strings::format("%s.%s", exported_dir_filename, format.extension()); + const fs::path exported_archive_path = (output_dir / exported_archive_filename); + + // -NoDefaultExcludes is needed for ".vcpkg-root" + const auto cmd_line = Strings::format(R"("%s" -E tar "cf" "%s" --format=%s -- "%s")", + cmake_exe.u8string(), + exported_archive_path.u8string(), + format.cmake_option(), + raw_exported_dir.u8string()); + + const int exit_code = System::cmd_execute_clean(cmd_line); + Checks::check_exit( + VCPKG_LINE_INFO, exit_code == 0, "Error: %s creation failed", exported_archive_path.generic_string()); + return exported_archive_path; + } + + static Optional<std::string> maybe_lookup(std::unordered_map<std::string, std::string> const& m, + std::string const& key) + { + const auto it = m.find(key); + if (it != m.end()) return it->second; + return nullopt; + } + + void export_integration_files(const fs::path& raw_exported_dir_path, const VcpkgPaths& paths) + { + const std::vector<fs::path> integration_files_relative_to_root = { + {".vcpkg-root"}, + {fs::path{"scripts"} / "buildsystems" / "msbuild" / "applocal.ps1"}, + {fs::path{"scripts"} / "buildsystems" / "msbuild" / "vcpkg.targets"}, + {fs::path{"scripts"} / "buildsystems" / "vcpkg.cmake"}, + {fs::path{"scripts"} / "cmake" / "vcpkg_get_windows_sdk.cmake"}, + }; + + for (const fs::path& file : integration_files_relative_to_root) + { + const fs::path source = paths.root / file; + fs::path destination = raw_exported_dir_path / file; + Files::Filesystem& fs = paths.get_filesystem(); + std::error_code ec; + fs.create_directories(destination.parent_path(), ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec); + fs.copy_file(source, destination, fs::copy_options::overwrite_existing, ec); + Checks::check_exit(VCPKG_LINE_INFO, !ec); + } + } + + struct ExportArguments + { + bool dry_run = false; + bool raw = false; + bool nuget = false; + bool ifw = false; + bool zip = false; + bool seven_zip = false; + + Optional<std::string> maybe_output; + + Optional<std::string> maybe_nuget_id; + Optional<std::string> maybe_nuget_version; + + IFW::Options ifw_options; + std::vector<PackageSpec> specs; + }; + + static constexpr StringLiteral OPTION_OUTPUT = "--output"; + static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; + static constexpr StringLiteral OPTION_RAW = "--raw"; + static constexpr StringLiteral OPTION_NUGET = "--nuget"; + static constexpr StringLiteral OPTION_IFW = "--ifw"; + static constexpr StringLiteral OPTION_ZIP = "--zip"; + static constexpr StringLiteral OPTION_SEVEN_ZIP = "--7zip"; + static constexpr StringLiteral OPTION_NUGET_ID = "--nuget-id"; + static constexpr StringLiteral OPTION_NUGET_VERSION = "--nuget-version"; + static constexpr StringLiteral OPTION_IFW_REPOSITORY_URL = "--ifw-repository-url"; + static constexpr StringLiteral OPTION_IFW_PACKAGES_DIR_PATH = "--ifw-packages-directory-path"; + static constexpr StringLiteral OPTION_IFW_REPOSITORY_DIR_PATH = "--ifw-repository-directory-path"; + static constexpr StringLiteral OPTION_IFW_CONFIG_FILE_PATH = "--ifw-configuration-file-path"; + static constexpr StringLiteral OPTION_IFW_INSTALLER_FILE_PATH = "--ifw-installer-file-path"; + + static constexpr std::array<CommandSwitch, 6> EXPORT_SWITCHES = {{ + {OPTION_DRY_RUN, "Do not actually export"}, + {OPTION_RAW, "Export to an uncompressed directory"}, + {OPTION_NUGET, "Export a NuGet package"}, + {OPTION_IFW, "Export to an IFW-based installer"}, + {OPTION_ZIP, "Export to a zip file"}, + {OPTION_SEVEN_ZIP, "Export to a 7zip (.7z) file"}, + }}; + + static constexpr std::array<CommandSetting, 8> EXPORT_SETTINGS = {{ + {OPTION_OUTPUT, "Specify the output name (used to construct filename)"}, + {OPTION_NUGET_ID, "Specify the id for the exported NuGet package (overrides --output)"}, + {OPTION_NUGET_VERSION, "Specify the version for the exported NuGet package"}, + {OPTION_IFW_REPOSITORY_URL, "Specify the remote repository URL for the online installer"}, + {OPTION_IFW_PACKAGES_DIR_PATH, "Specify the temporary directory path for the repacked packages"}, + {OPTION_IFW_REPOSITORY_DIR_PATH, "Specify the directory path for the exported repository"}, + {OPTION_IFW_CONFIG_FILE_PATH, "Specify the temporary file path for the installer configuration"}, + {OPTION_IFW_INSTALLER_FILE_PATH, "Specify the file path for the exported installer"}, + }}; + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("export zlib zlib:x64-windows boost --nuget"), + 0, + SIZE_MAX, + {EXPORT_SWITCHES, EXPORT_SETTINGS}, + nullptr, + }; + + static ExportArguments handle_export_command_arguments(const VcpkgCmdArguments& args, + const Triplet& default_triplet) + { + ExportArguments ret; + + const auto options = args.parse_arguments(COMMAND_STRUCTURE); + + // input sanitization + ret.specs = Util::fmap(args.command_arguments, [&](auto&& arg) { + return Input::check_and_get_package_spec(arg, default_triplet, COMMAND_STRUCTURE.example_text); + }); + ret.dry_run = options.switches.find(OPTION_DRY_RUN) != options.switches.cend(); + ret.raw = options.switches.find(OPTION_RAW) != options.switches.cend(); + ret.nuget = options.switches.find(OPTION_NUGET) != options.switches.cend(); + ret.ifw = options.switches.find(OPTION_IFW) != options.switches.cend(); + ret.zip = options.switches.find(OPTION_ZIP) != options.switches.cend(); + ret.seven_zip = options.switches.find(OPTION_SEVEN_ZIP) != options.switches.cend(); + + ret.maybe_output = maybe_lookup(options.settings, OPTION_OUTPUT); + + if (!ret.raw && !ret.nuget && !ret.ifw && !ret.zip && !ret.seven_zip && !ret.dry_run) + { + System::println(System::Color::error, + "Must provide at least one export type: --raw --nuget --ifw --zip --7zip"); + System::print(COMMAND_STRUCTURE.example_text); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + struct OptionPair + { + const std::string& name; + Optional<std::string>& out_opt; + }; + const auto options_implies = + [&](const std::string& main_opt_name, bool main_opt, Span<const OptionPair> implying_opts) { + if (main_opt) + { + for (auto&& opt : implying_opts) + opt.out_opt = maybe_lookup(options.settings, opt.name); + } + else + { + for (auto&& opt : implying_opts) + Checks::check_exit(VCPKG_LINE_INFO, + !maybe_lookup(options.settings, opt.name), + "%s is only valid with %s", + opt.name, + main_opt_name); + } + }; + + options_implies(OPTION_NUGET, + ret.nuget, + { + {OPTION_NUGET_ID, ret.maybe_nuget_id}, + {OPTION_NUGET_VERSION, ret.maybe_nuget_version}, + }); + + options_implies(OPTION_IFW, + ret.ifw, + { + {OPTION_IFW_REPOSITORY_URL, ret.ifw_options.maybe_repository_url}, + {OPTION_IFW_PACKAGES_DIR_PATH, ret.ifw_options.maybe_packages_dir_path}, + {OPTION_IFW_REPOSITORY_DIR_PATH, ret.ifw_options.maybe_repository_dir_path}, + {OPTION_IFW_CONFIG_FILE_PATH, ret.ifw_options.maybe_config_file_path}, + {OPTION_IFW_INSTALLER_FILE_PATH, ret.ifw_options.maybe_installer_file_path}, + }); + return ret; + } + + static void print_next_step_info(const fs::path& prefix) + { + const fs::path cmake_toolchain = prefix / "scripts" / "buildsystems" / "vcpkg.cmake"; + const System::CMakeVariable cmake_variable = + System::CMakeVariable("CMAKE_TOOLCHAIN_FILE", cmake_toolchain.generic_string()); + System::println("\n" + "To use the exported libraries in CMake projects use:" + "\n" + " %s" + "\n", + cmake_variable.s); + } + + static void handle_raw_based_export(Span<const ExportPlanAction> export_plan, + const ExportArguments& opts, + const std::string& export_id, + const VcpkgPaths& paths) + { + Files::Filesystem& fs = paths.get_filesystem(); + const fs::path export_to_path = paths.root; + const fs::path raw_exported_dir_path = export_to_path / export_id; + std::error_code ec; + fs.remove_all(raw_exported_dir_path, ec); + fs.create_directory(raw_exported_dir_path, ec); + + // execute the plan + for (const ExportPlanAction& action : export_plan) + { + if (action.plan_type != ExportPlanType::ALREADY_BUILT) + { + Checks::unreachable(VCPKG_LINE_INFO); + } + + const std::string display_name = action.spec.to_string(); + System::println("Exporting package %s... ", display_name); + + const BinaryParagraph& binary_paragraph = action.core_paragraph().value_or_exit(VCPKG_LINE_INFO); + + const InstallDir dirs = InstallDir::from_destination_root( + raw_exported_dir_path / "installed", + action.spec.triplet().to_string(), + raw_exported_dir_path / "installed" / "vcpkg" / "info" / (binary_paragraph.fullstem() + ".list")); + + Install::install_files_and_write_listfile(paths.get_filesystem(), paths.package_dir(action.spec), dirs); + System::println(System::Color::success, "Exporting package %s... done", display_name); + } + + // Copy files needed for integration + export_integration_files(raw_exported_dir_path, paths); + + if (opts.raw) + { + System::println( + System::Color::success, R"(Files exported at: "%s")", raw_exported_dir_path.generic_string()); + print_next_step_info(raw_exported_dir_path); + } + + if (opts.nuget) + { + System::println("Creating nuget package... "); + + const std::string nuget_id = opts.maybe_nuget_id.value_or(raw_exported_dir_path.filename().string()); + const std::string nuget_version = opts.maybe_nuget_version.value_or("1.0.0"); + const fs::path output_path = + do_nuget_export(paths, nuget_id, nuget_version, raw_exported_dir_path, export_to_path); + System::println(System::Color::success, "Creating nuget package... done"); + System::println(System::Color::success, "NuGet package exported at: %s", output_path.generic_string()); + + System::println(R"( +With a project open, go to Tools->NuGet Package Manager->Package Manager Console and paste: + Install-Package %s -Source "%s" +)" + "\n", + nuget_id, + output_path.parent_path().u8string()); + } + + if (opts.zip) + { + System::println("Creating zip archive... "); + const fs::path output_path = + do_archive_export(paths, raw_exported_dir_path, export_to_path, ArchiveFormatC::ZIP); + System::println(System::Color::success, "Creating zip archive... done"); + System::println(System::Color::success, "Zip archive exported at: %s", output_path.generic_string()); + print_next_step_info("[...]"); + } + + if (opts.seven_zip) + { + System::println("Creating 7zip archive... "); + const fs::path output_path = + do_archive_export(paths, raw_exported_dir_path, export_to_path, ArchiveFormatC::SEVEN_ZIP); + System::println(System::Color::success, "Creating 7zip archive... done"); + System::println(System::Color::success, "7zip archive exported at: %s", output_path.generic_string()); + print_next_step_info("[...]"); + } + + if (!opts.raw) + { + fs.remove_all(raw_exported_dir_path, ec); + } + } + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + const auto opts = handle_export_command_arguments(args, default_triplet); + for (auto&& spec : opts.specs) + Input::check_triplet(spec.triplet(), paths); + + // create the plan + const StatusParagraphs status_db = database_load_check(paths); + Dependencies::PathsPortFileProvider provider(paths); + std::vector<ExportPlanAction> export_plan = Dependencies::create_export_plan(opts.specs, status_db); + Checks::check_exit(VCPKG_LINE_INFO, !export_plan.empty(), "Export plan cannot be empty"); + + std::map<ExportPlanType, std::vector<const ExportPlanAction*>> group_by_plan_type; + Util::group_by(export_plan, &group_by_plan_type, [](const ExportPlanAction& p) { return p.plan_type; }); + print_plan(group_by_plan_type); + + const bool has_non_user_requested_packages = + Util::find_if(export_plan, [](const ExportPlanAction& package) -> bool { + return package.request_type != RequestType::USER_REQUESTED; + }) != export_plan.cend(); + + if (has_non_user_requested_packages) + { + System::println(System::Color::warning, + "Additional packages (*) need to be exported to complete this operation."); + } + + const auto it = group_by_plan_type.find(ExportPlanType::NOT_BUILT); + if (it != group_by_plan_type.cend() && !it->second.empty()) + { + System::println(System::Color::error, "There are packages that have not been built."); + + // No need to show all of them, just the user-requested ones. Dependency resolution will handle the rest. + std::vector<const ExportPlanAction*> unbuilt = it->second; + Util::erase_remove_if( + unbuilt, [](const ExportPlanAction* a) { return a->request_type != RequestType::USER_REQUESTED; }); + + const auto s = Strings::join(" ", unbuilt, [](const ExportPlanAction* a) { return a->spec.to_string(); }); + System::println("To build them, run:\n" + " vcpkg install %s", + s); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + if (opts.dry_run) + { + Checks::exit_success(VCPKG_LINE_INFO); + } + + std::string export_id = opts.maybe_output.value_or(create_export_id()); + + if (opts.raw || opts.nuget || opts.zip || opts.seven_zip) + { + handle_raw_based_export(export_plan, opts, export_id, paths); + } + + if (opts.ifw) + { + IFW::do_export(export_plan, export_id, opts.ifw_options, paths); + + print_next_step_info("@RootDir@/src/vcpkg"); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/globalstate.cpp b/toolsrc/src/vcpkg/globalstate.cpp new file mode 100644 index 000000000..a4100acf7 --- /dev/null +++ b/toolsrc/src/vcpkg/globalstate.cpp @@ -0,0 +1,16 @@ +#include "pch.h" + +#include <vcpkg/globalstate.h> + +namespace vcpkg +{ + Util::LockGuarded<Chrono::ElapsedTimer> GlobalState::timer; + Util::LockGuarded<std::string> GlobalState::g_surveydate; + + std::atomic<bool> GlobalState::debugging(false); + std::atomic<bool> GlobalState::feature_packages(true); + std::atomic<bool> GlobalState::g_binary_caching(false); + + std::atomic<int> GlobalState::g_init_console_cp(0); + std::atomic<int> GlobalState::g_init_console_output_cp(0); +} diff --git a/toolsrc/src/vcpkg/help.cpp b/toolsrc/src/vcpkg/help.cpp new file mode 100644 index 000000000..743619937 --- /dev/null +++ b/toolsrc/src/vcpkg/help.cpp @@ -0,0 +1,151 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/export.h> +#include <vcpkg/help.h> +#include <vcpkg/install.h> +#include <vcpkg/remove.h> + +namespace vcpkg::Help +{ + struct Topic + { + using topic_function = void (*)(const VcpkgPaths& paths); + + constexpr Topic(CStringView n, topic_function fn) : name(n), print(fn) {} + + CStringView name; + topic_function print; + }; + + template<const CommandStructure& S> + static void command_topic_fn(const VcpkgPaths&) + { + display_usage(S); + } + + static void integrate_topic_fn(const VcpkgPaths&) + { + System::print("Commands:\n" + "%s", + Commands::Integrate::INTEGRATE_COMMAND_HELPSTRING); + } + + static void help_topics(const VcpkgPaths&); + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("help"), + 0, + 1, + {}, + nullptr, + }; + + static constexpr std::array<Topic, 12> topics = {{ + {"create", command_topic_fn<Commands::Create::COMMAND_STRUCTURE>}, + {"edit", command_topic_fn<Commands::Edit::COMMAND_STRUCTURE>}, + {"env", command_topic_fn<Commands::Env::COMMAND_STRUCTURE>}, + {"export", command_topic_fn<Export::COMMAND_STRUCTURE>}, + {"help", command_topic_fn<Help::COMMAND_STRUCTURE>}, + {"install", command_topic_fn<Install::COMMAND_STRUCTURE>}, + {"integrate", integrate_topic_fn}, + {"list", command_topic_fn<Commands::List::COMMAND_STRUCTURE>}, + {"owns", command_topic_fn<Commands::Owns::COMMAND_STRUCTURE>}, + {"remove", command_topic_fn<Remove::COMMAND_STRUCTURE>}, + {"search", command_topic_fn<Commands::Search::COMMAND_STRUCTURE>}, + {"topics", help_topics}, + }}; + + static void help_topics(const VcpkgPaths&) + { + System::println("Available help topics:\n" + " triplet\n" + " integrate" + "%s", + Strings::join("", topics, [](const Topic& topic) { return std::string("\n ") + topic.name; })); + } + + void help_topic_valid_triplet(const VcpkgPaths& paths) + { + System::println("Available architecture triplets:"); + for (auto&& triplet : paths.get_available_triplets()) + { + System::println(" %s", triplet); + } + } + + void print_usage() + { + System::println( + "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 --outdated Uninstall all out-of-date packages\n" + " vcpkg list List installed packages\n" + " vcpkg update Display list of packages for updating\n" + " vcpkg upgrade Rebuild all outdated packages\n" + " vcpkg hash <file> [alg] Hash a file by specific algorithm, default SHA512\n" + " vcpkg help topics Display the list of help topics\n" + " vcpkg help <topic> Display help for a specific topic\n" + "\n" + "%s" // Integration help + "\n" + " vcpkg export <pkg>... [opt]... Exports a package\n" + " vcpkg edit <pkg> Open up a port for editing (uses %%EDITOR%%, default 'code')\n" + " vcpkg 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 env Creates a clean shell environment for development or compiling.\n" + " vcpkg version Display version information\n" + " vcpkg contact Display contact information to send feedback\n" + "\n" + "Options:\n" + " --triplet <t> Specify the target architecture triplet.\n" + " (default: %%VCPKG_DEFAULT_TRIPLET%%, 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.", + Commands::Integrate::INTEGRATE_COMMAND_HELPSTRING); + } + + std::string create_example_string(const std::string& command_and_arguments) + { + std::string cs = Strings::format("Example:\n" + " vcpkg %s\n", + command_and_arguments); + return cs; + } + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + args.parse_arguments(COMMAND_STRUCTURE); + + if (args.command_arguments.empty()) + { + print_usage(); + Checks::exit_success(VCPKG_LINE_INFO); + } + const auto& topic = args.command_arguments[0]; + if (topic == "triplet" || topic == "triplets" || topic == "triple") + { + help_topic_valid_triplet(paths); + Checks::exit_success(VCPKG_LINE_INFO); + } + + auto it_topic = Util::find_if(topics, [&](const Topic& t) { return t.name == topic; }); + if (it_topic != topics.end()) + { + it_topic->print(paths); + Checks::exit_success(VCPKG_LINE_INFO); + } + + System::println(System::Color::error, "Error: unknown topic %s", topic); + help_topics(paths); + Checks::exit_fail(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/input.cpp b/toolsrc/src/vcpkg/input.cpp new file mode 100644 index 000000000..aee0fac7f --- /dev/null +++ b/toolsrc/src/vcpkg/input.cpp @@ -0,0 +1,55 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/metrics.h> + +namespace vcpkg::Input +{ + PackageSpec check_and_get_package_spec(const std::string& package_spec_as_string, + const Triplet& default_triplet, + CStringView example_text) + { + const std::string as_lowercase = Strings::ascii_to_lowercase(package_spec_as_string); + auto expected_spec = FullPackageSpec::from_string(as_lowercase, default_triplet); + if (const auto spec = expected_spec.get()) + { + return PackageSpec{spec->package_spec}; + } + + // Intentionally show the lowercased string + System::println(System::Color::error, "Error: %s: %s", vcpkg::to_string(expected_spec.error()), as_lowercase); + System::print(example_text); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + void check_triplet(const Triplet& t, const VcpkgPaths& paths) + { + if (!paths.is_valid_triplet(t)) + { + System::println(System::Color::error, "Error: invalid triplet: %s", t); + Metrics::g_metrics.lock()->track_property("error", "invalid triplet: " + t.to_string()); + Help::help_topic_valid_triplet(paths); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + + FullPackageSpec check_and_get_full_package_spec(const std::string& full_package_spec_as_string, + const Triplet& default_triplet, + CStringView example_text) + { + const std::string as_lowercase = Strings::ascii_to_lowercase(full_package_spec_as_string); + auto expected_spec = FullPackageSpec::from_string(as_lowercase, default_triplet); + if (const auto spec = expected_spec.get()) + { + return *spec; + } + + // Intentionally show the lowercased string + System::println(System::Color::error, "Error: %s: %s", vcpkg::to_string(expected_spec.error()), as_lowercase); + System::print(example_text); + Checks::exit_fail(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp new file mode 100644 index 000000000..40e696fa0 --- /dev/null +++ b/toolsrc/src/vcpkg/install.cpp @@ -0,0 +1,741 @@ +#include "pch.h" + +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/install.h> +#include <vcpkg/metrics.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/remove.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Install +{ + using namespace Dependencies; + + InstallDir InstallDir::from_destination_root(const fs::path& destination_root, + const std::string& destination_subdirectory, + const fs::path& listfile) + { + InstallDir dirs; + dirs.m_destination = destination_root / destination_subdirectory; + dirs.m_destination_subdirectory = destination_subdirectory; + dirs.m_listfile = listfile; + return dirs; + } + + const fs::path& InstallDir::destination() const { return this->m_destination; } + + const std::string& InstallDir::destination_subdirectory() const { return this->m_destination_subdirectory; } + + const fs::path& InstallDir::listfile() const { return this->m_listfile; } + + void install_files_and_write_listfile(Files::Filesystem& fs, + const fs::path& source_dir, + const InstallDir& destination_dir) + { + std::vector<std::string> output; + std::error_code ec; + + const size_t prefix_length = source_dir.native().size(); + const fs::path& destination = destination_dir.destination(); + const std::string& destination_subdirectory = destination_dir.destination_subdirectory(); + const fs::path& listfile = destination_dir.listfile(); + + Checks::check_exit( + VCPKG_LINE_INFO, fs.exists(source_dir), "Source directory %s does not exist", source_dir.generic_string()); + fs.create_directories(destination, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not create destination directory %s", destination.generic_string()); + const fs::path listfile_parent = listfile.parent_path(); + fs.create_directories(listfile_parent, ec); + Checks::check_exit( + VCPKG_LINE_INFO, !ec, "Could not create directory for listfile %s", listfile.generic_string()); + + output.push_back(Strings::format(R"(%s/)", destination_subdirectory)); + auto files = fs.get_files_recursive(source_dir); + for (auto&& file : files) + { + const auto status = fs.status(file, ec); + if (ec) + { + System::println(System::Color::error, "failed: %s: %s", file.u8string(), ec.message()); + continue; + } + + const std::string filename = file.filename().u8string(); + if (fs::is_regular_file(status) && (Strings::case_insensitive_ascii_equals(filename, "CONTROL") || + Strings::case_insensitive_ascii_equals(filename, "BUILD_INFO"))) + { + // Do not copy the control file + continue; + } + + const std::string suffix = file.generic_u8string().substr(prefix_length + 1); + const fs::path target = destination / suffix; + + switch (status.type()) + { + case fs::file_type::directory: + { + fs.create_directory(target, ec); + if (ec) + { + System::println(System::Color::error, "failed: %s: %s", target.u8string(), ec.message()); + } + + // Trailing backslash for directories + output.push_back(Strings::format(R"(%s/%s/)", destination_subdirectory, suffix)); + break; + } + case fs::file_type::regular: + { + if (fs.exists(target)) + { + System::println(System::Color::warning, + "File %s was already present and will be overwritten", + target.u8string(), + ec.message()); + } + fs.copy_file(file, target, fs::copy_options::overwrite_existing, ec); + if (ec) + { + System::println(System::Color::error, "failed: %s: %s", target.u8string(), ec.message()); + } + output.push_back(Strings::format(R"(%s/%s)", destination_subdirectory, suffix)); + break; + } + default: + System::println(System::Color::error, "failed: %s: cannot handle file type", file.u8string()); + break; + } + } + + std::sort(output.begin(), output.end()); + + fs.write_lines(listfile, output); + } + + static void remove_first_n_chars(std::vector<std::string>* strings, const size_t n) + { + for (std::string& s : *strings) + { + s.erase(0, n); + } + }; + + static std::vector<std::string> extract_files_in_triplet( + const std::vector<StatusParagraphAndAssociatedFiles>& pgh_and_files, const Triplet& triplet) + { + std::vector<std::string> output; + for (const StatusParagraphAndAssociatedFiles& t : pgh_and_files) + { + if (t.pgh.package.spec.triplet() != triplet) + { + continue; + } + + Util::Vectors::concatenate(&output, t.files); + } + + std::sort(output.begin(), output.end()); + return output; + } + + static SortedVector<std::string> build_list_of_package_files(const Files::Filesystem& fs, + const fs::path& package_dir) + { + const std::vector<fs::path> package_file_paths = fs.get_files_recursive(package_dir); + const size_t package_remove_char_count = package_dir.generic_string().size() + 1; // +1 for the slash + auto package_files = Util::fmap(package_file_paths, [package_remove_char_count](const fs::path& path) { + std::string as_string = path.generic_string(); + as_string.erase(0, package_remove_char_count); + return std::move(as_string); + }); + + return SortedVector<std::string>(std::move(package_files)); + } + + static SortedVector<std::string> build_list_of_installed_files( + const std::vector<StatusParagraphAndAssociatedFiles>& pgh_and_files, const Triplet& triplet) + { + std::vector<std::string> installed_files = extract_files_in_triplet(pgh_and_files, triplet); + const size_t installed_remove_char_count = triplet.canonical_name().size() + 1; // +1 for the slash + remove_first_n_chars(&installed_files, installed_remove_char_count); + + return SortedVector<std::string>(std::move(installed_files)); + } + + InstallResult install_package(const VcpkgPaths& paths, const BinaryControlFile& bcf, StatusParagraphs* status_db) + { + const fs::path package_dir = paths.package_dir(bcf.core_paragraph.spec); + const Triplet& triplet = bcf.core_paragraph.spec.triplet(); + const std::vector<StatusParagraphAndAssociatedFiles> pgh_and_files = get_installed_files(paths, *status_db); + + const SortedVector<std::string> package_files = + build_list_of_package_files(paths.get_filesystem(), package_dir); + const SortedVector<std::string> installed_files = build_list_of_installed_files(pgh_and_files, triplet); + + std::vector<std::string> intersection; + std::set_intersection(package_files.begin(), + package_files.end(), + installed_files.begin(), + installed_files.end(), + std::back_inserter(intersection)); + + if (!intersection.empty()) + { + const fs::path triplet_install_path = paths.installed / triplet.canonical_name(); + System::println(System::Color::error, + "The following files are already installed in %s and are in conflict with %s", + triplet_install_path.generic_string(), + bcf.core_paragraph.spec); + System::print("\n "); + System::println(Strings::join("\n ", intersection)); + System::println(); + return InstallResult::FILE_CONFLICTS; + } + + StatusParagraph source_paragraph; + source_paragraph.package = bcf.core_paragraph; + source_paragraph.want = Want::INSTALL; + source_paragraph.state = InstallState::HALF_INSTALLED; + + write_update(paths, source_paragraph); + status_db->insert(std::make_unique<StatusParagraph>(source_paragraph)); + + std::vector<StatusParagraph> features_spghs; + for (auto&& feature : bcf.features) + { + features_spghs.emplace_back(); + + StatusParagraph& feature_paragraph = features_spghs.back(); + feature_paragraph.package = feature; + feature_paragraph.want = Want::INSTALL; + feature_paragraph.state = InstallState::HALF_INSTALLED; + + write_update(paths, feature_paragraph); + status_db->insert(std::make_unique<StatusParagraph>(feature_paragraph)); + } + + const InstallDir install_dir = InstallDir::from_destination_root( + paths.installed, triplet.to_string(), paths.listfile_path(bcf.core_paragraph)); + + install_files_and_write_listfile(paths.get_filesystem(), package_dir, install_dir); + + source_paragraph.state = InstallState::INSTALLED; + write_update(paths, source_paragraph); + status_db->insert(std::make_unique<StatusParagraph>(source_paragraph)); + + for (auto&& feature_paragraph : features_spghs) + { + feature_paragraph.state = InstallState::INSTALLED; + write_update(paths, feature_paragraph); + status_db->insert(std::make_unique<StatusParagraph>(feature_paragraph)); + } + + return InstallResult::SUCCESS; + } + + using Build::BuildResult; + using Build::ExtendedBuildResult; + + ExtendedBuildResult perform_install_plan_action(const VcpkgPaths& paths, + const InstallPlanAction& action, + StatusParagraphs& status_db) + { + const InstallPlanType& plan_type = action.plan_type; + const std::string display_name = action.spec.to_string(); + const std::string display_name_with_features = + GlobalState::feature_packages ? action.displayname() : display_name; + + const bool is_user_requested = action.request_type == RequestType::USER_REQUESTED; + const bool use_head_version = Util::Enum::to_bool(action.build_options.use_head_version); + + if (plan_type == InstallPlanType::ALREADY_INSTALLED) + { + if (use_head_version && is_user_requested) + System::println( + System::Color::warning, "Package %s is already installed -- not building from HEAD", display_name); + else + System::println(System::Color::success, "Package %s is already installed", display_name); + return BuildResult::SUCCEEDED; + } + + auto aux_install = [&](const std::string& name, const BinaryControlFile& bcf) -> BuildResult { + System::println("Installing package %s... ", name); + const auto install_result = install_package(paths, bcf, &status_db); + switch (install_result) + { + case InstallResult::SUCCESS: + System::println(System::Color::success, "Installing package %s... done", name); + return BuildResult::SUCCEEDED; + case InstallResult::FILE_CONFLICTS: return BuildResult::FILE_CONFLICTS; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + }; + + if (plan_type == InstallPlanType::BUILD_AND_INSTALL) + { + if (use_head_version) + System::println("Building package %s from HEAD... ", display_name_with_features); + else + System::println("Building package %s... ", display_name_with_features); + + auto result = [&]() -> Build::ExtendedBuildResult { + const Build::BuildPackageConfig build_config{action.source_control_file.value_or_exit(VCPKG_LINE_INFO), + action.spec.triplet(), + paths.port_dir(action.spec), + action.build_options, + action.feature_list}; + return Build::build_package(paths, build_config, status_db); + }(); + + if (result.code != Build::BuildResult::SUCCEEDED) + { + System::println(System::Color::error, Build::create_error_message(result.code, action.spec)); + return result; + } + + System::println("Building package %s... done", display_name_with_features); + + auto bcf = std::make_unique<BinaryControlFile>( + Paragraphs::try_load_cached_package(paths, action.spec).value_or_exit(VCPKG_LINE_INFO)); + auto code = aux_install(display_name_with_features, *bcf); + + if (action.build_options.clean_packages == Build::CleanPackages::YES) + { + auto& fs = paths.get_filesystem(); + const fs::path package_dir = paths.package_dir(action.spec); + std::error_code ec; + fs.remove_all(package_dir, ec); + } + + return {code, std::move(bcf)}; + } + + if (plan_type == InstallPlanType::EXCLUDED) + { + System::println(System::Color::warning, "Package %s is excluded", display_name); + return BuildResult::EXCLUDED; + } + + Checks::unreachable(VCPKG_LINE_INFO); + } + + void InstallSummary::print() const + { + System::println("RESULTS"); + + for (const SpecSummary& result : this->results) + { + System::println(" %s: %s: %s", result.spec, Build::to_string(result.build_result.code), result.timing); + } + + std::map<BuildResult, int> summary; + for (const BuildResult& v : Build::BUILD_RESULT_VALUES) + { + summary[v] = 0; + } + + for (const SpecSummary& r : this->results) + { + summary[r.build_result.code]++; + } + + System::println("\nSUMMARY"); + for (const std::pair<const BuildResult, int>& entry : summary) + { + System::println(" %s: %d", Build::to_string(entry.first), entry.second); + } + } + + InstallSummary perform(const std::vector<AnyAction>& action_plan, + const KeepGoing keep_going, + const VcpkgPaths& paths, + StatusParagraphs& status_db) + { + std::vector<SpecSummary> results; + + const auto timer = Chrono::ElapsedTimer::create_started(); + size_t counter = 0; + const size_t package_count = action_plan.size(); + + for (const auto& action : action_plan) + { + const auto build_timer = Chrono::ElapsedTimer::create_started(); + counter++; + + const PackageSpec& spec = action.spec(); + const std::string display_name = spec.to_string(); + System::println("Starting package %zd/%zd: %s", counter, package_count, display_name); + + results.emplace_back(spec, &action); + + if (const auto install_action = action.install_action.get()) + { + auto result = perform_install_plan_action(paths, *install_action, status_db); + + if (result.code != BuildResult::SUCCEEDED && keep_going == KeepGoing::NO) + { + System::println(Build::create_user_troubleshooting_message(install_action->spec)); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + results.back().build_result = std::move(result); + } + else if (const auto remove_action = action.remove_action.get()) + { + Remove::perform_remove_plan_action(paths, *remove_action, Remove::Purge::YES, &status_db); + } + else + { + Checks::unreachable(VCPKG_LINE_INFO); + } + + results.back().timing = build_timer.elapsed(); + System::println("Elapsed time for package %s: %s", display_name, results.back().timing.to_string()); + } + + return InstallSummary{std::move(results), timer.to_string()}; + } + + static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; + static constexpr StringLiteral OPTION_USE_HEAD_VERSION = "--head"; + static constexpr StringLiteral OPTION_NO_DOWNLOADS = "--no-downloads"; + static constexpr StringLiteral OPTION_RECURSE = "--recurse"; + static constexpr StringLiteral OPTION_KEEP_GOING = "--keep-going"; + static constexpr StringLiteral OPTION_XUNIT = "--x-xunit"; + static constexpr StringLiteral OPTION_USE_ARIA2 = "--x-use-aria2"; + + static constexpr std::array<CommandSwitch, 6> INSTALL_SWITCHES = {{ + {OPTION_DRY_RUN, "Do not actually build or install"}, + {OPTION_USE_HEAD_VERSION, "Install the libraries on the command line using the latest upstream sources"}, + {OPTION_NO_DOWNLOADS, "Do not download new sources"}, + {OPTION_RECURSE, "Allow removal of packages as part of installation"}, + {OPTION_KEEP_GOING, "Continue installing packages on failure"}, + {OPTION_USE_ARIA2, "Use aria2 to perform download tasks"}, + }}; + static constexpr std::array<CommandSetting, 1> INSTALL_SETTINGS = {{ + {OPTION_XUNIT, "File to output results in XUnit format (Internal use)"}, + }}; + + std::vector<std::string> get_all_port_names(const VcpkgPaths& paths) + { + auto sources_and_errors = Paragraphs::try_load_all_ports(paths.get_filesystem(), paths.ports); + + return Util::fmap(sources_and_errors.paragraphs, + [](auto&& pgh) -> std::string { return pgh->core_paragraph->name; }); + } + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("install zlib zlib:x64-windows curl boost"), + 1, + SIZE_MAX, + {INSTALL_SWITCHES, INSTALL_SETTINGS}, + &get_all_port_names, + }; + + static void print_cmake_information(const BinaryParagraph& bpgh, const VcpkgPaths& paths) + { + static const std::regex cmake_library_regex(R"(\badd_library\(([^\s\)]+)\s)", std::regex_constants::ECMAScript); + + auto& fs = paths.get_filesystem(); + + auto usage_file = paths.installed / bpgh.spec.triplet().canonical_name() / "share" / bpgh.spec.name() / "usage"; + if (fs.exists(usage_file)) + { + auto maybe_contents = fs.read_contents(usage_file); + if (auto p_contents = maybe_contents.get()) + { + System::println(*p_contents); + } + return; + } + + auto files = fs.read_lines(paths.listfile_path(bpgh)); + if (auto p_lines = files.get()) + { + std::map<std::string, std::string> config_files; + std::map<std::string, std::vector<std::string>> library_targets; + + for (auto&& suffix : *p_lines) + { + if (Strings::case_insensitive_ascii_contains(suffix, "/share/") && Strings::ends_with(suffix, ".cmake")) + { + // CMake file is inside the share folder + auto path = paths.installed / suffix; + auto maybe_contents = fs.read_contents(path); + auto find_package_name = path.parent_path().filename().u8string(); + if (auto p_contents = maybe_contents.get()) + { + std::sregex_iterator next(p_contents->begin(), p_contents->end(), cmake_library_regex); + std::sregex_iterator last; + + while (next != last) + { + auto match = *next; + library_targets[find_package_name].push_back(match[1]); + ++next; + } + } + + auto filename = fs::u8path(suffix).filename().u8string(); + + if (Strings::ends_with(filename, "Config.cmake")) + { + auto root = filename.substr(0, filename.size() - 12); + if (Strings::case_insensitive_ascii_equals(root, find_package_name)) + config_files[find_package_name] = root; + } + else if (Strings::ends_with(filename, "-config.cmake")) + { + auto root = filename.substr(0, filename.size() - 13); + if (Strings::case_insensitive_ascii_equals(root, find_package_name)) + config_files[find_package_name] = root; + } + } + } + + if (library_targets.empty()) + { + } + else + { + System::println("The package %s provides CMake targets:\n", bpgh.spec); + + for (auto&& library_target_pair : library_targets) + { + auto config_it = config_files.find(library_target_pair.first); + if (config_it != config_files.end()) + System::println(" find_package(%s CONFIG REQUIRED)", config_it->second); + else + System::println(" find_package(%s CONFIG REQUIRED)", library_target_pair.first); + + std::sort(library_target_pair.second.begin(), + library_target_pair.second.end(), + [](const std::string& l, const std::string& r) { + if (l.size() < r.size()) return true; + if (l.size() > r.size()) return false; + return l < r; + }); + + if (library_target_pair.second.size() <= 4) + { + System::println(" target_link_libraries(main PRIVATE %s)\n", + Strings::join(" ", library_target_pair.second)); + } + else + { + auto omitted = library_target_pair.second.size() - 4; + library_target_pair.second.erase(library_target_pair.second.begin() + 4, + library_target_pair.second.end()); + System::println(" # Note: %zd target(s) were omitted.\n" + " target_link_libraries(main PRIVATE %s)\n", + omitted, + Strings::join(" ", library_target_pair.second)); + } + } + } + } + } + + /// + /// <summary> + /// Run "install" command. + /// </summary> + /// + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + // input sanitization + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + const std::vector<FullPackageSpec> specs = Util::fmap(args.command_arguments, [&](auto&& arg) { + return Input::check_and_get_full_package_spec(arg, default_triplet, COMMAND_STRUCTURE.example_text); + }); + + for (auto&& spec : specs) + { + Input::check_triplet(spec.package_spec.triplet(), paths); + if (!spec.features.empty() && !GlobalState::feature_packages) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, "Feature packages are experimentally available under the --featurepackages flag."); + } + } + + const bool dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); + const bool use_head_version = Util::Sets::contains(options.switches, (OPTION_USE_HEAD_VERSION)); + const bool no_downloads = Util::Sets::contains(options.switches, (OPTION_NO_DOWNLOADS)); + const bool is_recursive = Util::Sets::contains(options.switches, (OPTION_RECURSE)); + const bool use_aria2 = Util::Sets::contains(options.switches, (OPTION_USE_ARIA2)); + const KeepGoing keep_going = to_keep_going(Util::Sets::contains(options.switches, OPTION_KEEP_GOING)); + + // create the plan + StatusParagraphs status_db = database_load_check(paths); + + Build::DownloadTool download_tool = Build::DownloadTool::BUILT_IN; + if (use_aria2) download_tool = Build::DownloadTool::ARIA2; + + const Build::BuildPackageOptions install_plan_options = { + Util::Enum::to_enum<Build::UseHeadVersion>(use_head_version), + Util::Enum::to_enum<Build::AllowDownloads>(!no_downloads), + Build::CleanBuildtrees::NO, + Build::CleanPackages::NO, + download_tool}; + + auto all_ports = Paragraphs::load_all_ports(paths.get_filesystem(), paths.ports); + std::unordered_map<std::string, SourceControlFile> scf_map; + for (auto&& port : all_ports) + scf_map[port->core_paragraph->name] = std::move(*port); + MapPortFileProvider provider(scf_map); + + // Note: action_plan will hold raw pointers to SourceControlFiles from this map + std::vector<AnyAction> action_plan = + create_feature_install_plan(provider, FullPackageSpec::to_feature_specs(specs), status_db); + + if (!GlobalState::feature_packages) + { + for (auto&& action : action_plan) + { + if (action.remove_action.has_value()) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "The installation plan requires feature packages support. Please re-run the " + "command with --featurepackages."); + } + } + } + + for (auto&& action : action_plan) + { + if (auto p_install = action.install_action.get()) + { + p_install->build_options = install_plan_options; + if (p_install->request_type != RequestType::USER_REQUESTED) + p_install->build_options.use_head_version = Build::UseHeadVersion::NO; + } + } + + // install plan will be empty if it is already installed - need to change this at status paragraph part + Checks::check_exit(VCPKG_LINE_INFO, !action_plan.empty(), "Install plan cannot be empty"); + + // log the plan + const std::string specs_string = Strings::join(",", action_plan, [](const AnyAction& action) { + if (auto iaction = action.install_action.get()) + return iaction->spec.to_string(); + else if (auto raction = action.remove_action.get()) + return "R$" + raction->spec.to_string(); + Checks::unreachable(VCPKG_LINE_INFO); + }); + + Metrics::g_metrics.lock()->track_property("installplan", specs_string); + + Dependencies::print_plan(action_plan, is_recursive); + + if (dry_run) + { + Checks::exit_success(VCPKG_LINE_INFO); + } + + const InstallSummary summary = perform(action_plan, keep_going, paths, status_db); + + System::println("\nTotal elapsed time: %s\n", summary.total_elapsed_time); + + if (keep_going == KeepGoing::YES) + { + summary.print(); + } + + auto it_xunit = options.settings.find(OPTION_XUNIT); + if (it_xunit != options.settings.end()) + { + std::string xunit_doc = "<assemblies><assembly><collection>\n"; + + xunit_doc += summary.xunit_results(); + + xunit_doc += "</collection></assembly></assemblies>\n"; + paths.get_filesystem().write_contents(fs::u8path(it_xunit->second), xunit_doc); + } + + for (auto&& result : summary.results) + { + if (!result.action) continue; + if (auto p_install_action = result.action->install_action.get()) + { + if (p_install_action->request_type != RequestType::USER_REQUESTED) continue; + auto bpgh = result.get_binary_paragraph(); + if (!bpgh) continue; + print_cmake_information(*bpgh, paths); + } + } + + Checks::exit_success(VCPKG_LINE_INFO); + } + + SpecSummary::SpecSummary(const PackageSpec& spec, const Dependencies::AnyAction* action) + : spec(spec), build_result{BuildResult::NULLVALUE, nullptr}, action(action) + { + } + + const BinaryParagraph* SpecSummary::get_binary_paragraph() const + { + if (build_result.binary_control_file) return &build_result.binary_control_file->core_paragraph; + if (action) + if (auto p_install_plan = action->install_action.get()) + { + if (auto p_status = p_install_plan->installed_package.get()) + { + return &p_status->core->package; + } + } + return nullptr; + } + + std::string InstallSummary::xunit_result(const PackageSpec& spec, Chrono::ElapsedTime time, BuildResult code) + { + std::string inner_block; + const char* result_string = ""; + switch (code) + { + case BuildResult::POST_BUILD_CHECKS_FAILED: + case BuildResult::FILE_CONFLICTS: + case BuildResult::BUILD_FAILED: + result_string = "Fail"; + inner_block = Strings::format("<failure><message><![CDATA[%s]]></message></failure>", to_string(code)); + break; + case BuildResult::EXCLUDED: + case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: + result_string = "Skip"; + inner_block = Strings::format("<reason><![CDATA[%s]]></reason>", to_string(code)); + break; + case BuildResult::SUCCEEDED: result_string = "Pass"; break; + default: Checks::exit_fail(VCPKG_LINE_INFO); + } + + return Strings::format(R"(<test name="%s" method="%s" time="%lld" result="%s">%s</test>)" + "\n", + spec, + spec, + time.as<std::chrono::seconds>().count(), + result_string, + inner_block); + } + + std::string InstallSummary::xunit_results() const + { + std::string xunit_doc; + for (auto&& result : results) + { + xunit_doc += xunit_result(result.spec, result.timing, result.build_result.code); + } + return xunit_doc; + } +} diff --git a/toolsrc/src/vcpkg/metrics.cpp b/toolsrc/src/vcpkg/metrics.cpp new file mode 100644 index 000000000..8890c067f --- /dev/null +++ b/toolsrc/src/vcpkg/metrics.cpp @@ -0,0 +1,449 @@ +#include "pch.h" + +#include <vcpkg/commands.h> +#include <vcpkg/metrics.h> + +#include <vcpkg/base/chrono.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/system.h> + +#pragma comment(lib, "version") +#pragma comment(lib, "winhttp") + +namespace vcpkg::Metrics +{ + Util::LockGuarded<Metrics> g_metrics; + + static std::string get_current_date_time() + { + auto maybe_time = Chrono::CTime::get_current_date_time(); + if (auto ptime = maybe_time.get()) + { + return ptime->to_string(); + } + + return ""; + } + + static std::string generate_random_UUID() + { + int part_sizes[] = {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 < part_sizes[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 = generate_random_UUID(); + 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 || static_cast<unsigned char>(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() + { +#if defined(_WIN32) + std::wstring path; + path.resize(MAX_PATH); + const auto n = GetSystemDirectoryW(&path[0], static_cast<UINT>(path.size())); + path.resize(n); + path += L"\\kernel32.dll"; + + const 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))); +#else + return "unknown"; +#endif + } + + struct MetricMessage + { + std::string user_id = generate_random_UUID(); + std::string user_timestamp; + std::string timestamp = get_current_date_time(); + std::string properties; + std::string measurements; + + std::vector<std::string> buildtime_names; + std::vector<std::string> buildtime_times; + + void track_property(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 track_metric(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)); + } + + void track_buildtime(const std::string& name, double value) + { + buildtime_names.push_back(name); + buildtime_times.push_back(std::to_string(value)); + } + + std::string format_event_data_template() const + { + auto props_plus_buildtimes = properties; + if (buildtime_names.size() > 0) + { + if (props_plus_buildtimes.size() > 0) props_plus_buildtimes.push_back(','); + props_plus_buildtimes.append(Strings::format(R"("buildnames": [%s], "buildtimes": [%s])", + Strings::join(",", buildtime_names, to_json_string), + Strings::join(",", buildtime_times))); + } + + 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": "Other", + "ai.device.osVersion": "%s-%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, +#if defined(_WIN32) + "Windows", +#elif defined(__APPLE__) + "OSX", +#elif defined(__linux__) + "Linux", +#elif defined(__unix__) + "Unix", +#else + "Other", +#endif + get_os_version_string(), + session_id, + user_id, + user_timestamp, + props_plus_buildtimes, + 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 get_compiled_metrics_enabled() { return DISABLE_METRICS == 0; } + + std::string get_MAC_user() + { +#if defined(_WIN32) + auto getmac = System::cmd_execute_and_capture_output("getmac"); + + if (getmac.exit_code != 0) return "0"; + + std::regex mac_regex("([a-fA-F0-9]{2}(-[a-fA-F0-9]{2}){5})"); + std::sregex_iterator next(getmac.output.begin(), getmac.output.end(), mac_regex); + std::sregex_iterator last; + + while (next != last) + { + const auto match = *next; + if (match[0] != "00-00-00-00-00-00") + { + return vcpkg::Commands::Hash::get_string_hash(match[0], "SHA256"); + } + ++next; + } + + return "0"; +#else + return "{}"; +#endif + } + + void Metrics::set_user_information(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 Metrics::init_user_information(std::string& user_id, std::string& first_use_time) + { + user_id = generate_random_UUID(); + first_use_time = get_current_date_time(); + } + + void Metrics::set_send_metrics(bool should_send_metrics) { g_should_send_metrics = should_send_metrics; } + + void Metrics::set_print_metrics(bool should_print_metrics) { g_should_print_metrics = should_print_metrics; } + + void Metrics::track_metric(const std::string& name, double value) { g_metricmessage.track_metric(name, value); } + + void Metrics::track_buildtime(const std::string& name, double value) + { + g_metricmessage.track_buildtime(name, value); + } + + void Metrics::track_property(const std::string& name, const std::string& value) + { + g_metricmessage.track_property(name, value); + } + + void Metrics::upload(const std::string& payload) + { +#if defined(_WIN32) + HINTERNET connect = nullptr, request = nullptr; + BOOL results = FALSE; + + const HINTERNET session = WinHttpOpen( + L"vcpkg/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (session) connect = WinHttpConnect(session, L"dc.services.visualstudio.com", INTERNET_DEFAULT_HTTPS_PORT, 0); + + if (connect) + request = WinHttpOpenRequest(connect, + L"POST", + L"/v2/track", + nullptr, + WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + WINHTTP_FLAG_SECURE); + + if (request) + { + if (MAXDWORD <= payload.size()) abort(); + std::wstring hdrs = L"Content-Type: application/json\r\n"; + std::string& p = const_cast<std::string&>(payload); + results = WinHttpSendRequest(request, + hdrs.c_str(), + static_cast<DWORD>(hdrs.size()), + static_cast<void*>(&p[0]), + static_cast<DWORD>(payload.size()), + static_cast<DWORD>(payload.size()), + 0); + } + + if (results) + { + results = WinHttpReceiveResponse(request, nullptr); + } + + DWORD http_code = 0, junk = sizeof(DWORD); + + if (results) + { + results = WinHttpQueryHeaders(request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + nullptr, + &http_code, + &junk, + WINHTTP_NO_HEADER_INDEX); + } + + std::vector<char> response_buffer; + if (results) + { + DWORD available_data = 0, read_data = 0, total_data = 0; + while ((results = WinHttpQueryDataAvailable(request, &available_data)) == TRUE && available_data > 0) + { + response_buffer.resize(response_buffer.size() + available_data); + + results = WinHttpReadData(request, &response_buffer.data()[total_data], available_data, &read_data); + + if (!results) + { + break; + } + + total_data += read_data; + + response_buffer.resize(total_data); + } + } + + if (!results) + { +#ifndef NDEBUG + __debugbreak(); + auto err = GetLastError(); + std::cerr << "[DEBUG] failed to connect to server: " << err << "\n"; +#endif + } + + if (request) WinHttpCloseHandle(request); + if (connect) WinHttpCloseHandle(connect); + if (session) WinHttpCloseHandle(session); +#endif + } + + void Metrics::flush() + { + const std::string payload = g_metricmessage.format_event_data_template(); + if (g_should_print_metrics) std::cerr << payload << "\n"; + if (!g_should_send_metrics) return; + +#if defined(_WIN32) + wchar_t temp_folder[MAX_PATH]; + GetTempPathW(MAX_PATH, temp_folder); + + const fs::path temp_folder_path = fs::path(temp_folder) / "vcpkg"; + const fs::path temp_folder_path_exe = + temp_folder_path / Strings::format("vcpkgmetricsuploader-%s.exe", Commands::Version::base_version()); +#endif + + auto& fs = Files::get_real_filesystem(); + +#if defined(_WIN32) + + const fs::path exe_path = [&fs]() -> fs::path { + auto vcpkgdir = System::get_exe_path_of_current_process().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 ""; + }(); + + std::error_code ec; + fs.create_directories(temp_folder_path, ec); + if (ec) return; + fs.copy_file(exe_path, temp_folder_path_exe, fs::copy_options::skip_existing, ec); + if (ec) return; +#else + if (!fs.exists("/tmp")) return; + const fs::path temp_folder_path = "/tmp/vcpkg"; + std::error_code ec; + fs.create_directory(temp_folder_path, ec); + // ignore error + ec.clear(); +#endif + const fs::path vcpkg_metrics_txt_path = temp_folder_path / ("vcpkg" + generate_random_UUID() + ".txt"); + fs.write_contents(vcpkg_metrics_txt_path, payload, ec); + if (ec) return; + +#if defined(_WIN32) + const std::string cmd_line = Strings::format("start \"vcpkgmetricsuploader.exe\" \"%s\" \"%s\"", + temp_folder_path_exe.u8string(), + vcpkg_metrics_txt_path.u8string()); +#else + auto escaped_path = Strings::escape_string(vcpkg_metrics_txt_path.u8string(), '\'', '\\'); + const std::string cmd_line = Strings::format( + R"((curl "https://dc.services.visualstudio.com/v2/track" -H "Content-Type: application/json" -X POST --data '@%s' >/dev/null 2>&1; rm '%s') &)", + escaped_path, + escaped_path); +#endif + System::cmd_execute_clean(cmd_line); + } +} diff --git a/toolsrc/src/vcpkg/packagespec.cpp b/toolsrc/src/vcpkg/packagespec.cpp new file mode 100644 index 000000000..789aaca80 --- /dev/null +++ b/toolsrc/src/vcpkg/packagespec.cpp @@ -0,0 +1,195 @@ +#include "pch.h" + +#include <vcpkg/base/util.h> +#include <vcpkg/packagespec.h> +#include <vcpkg/packagespecparseresult.h> +#include <vcpkg/parse.h> + +using vcpkg::Parse::parse_comma_list; + +namespace vcpkg +{ + static bool is_valid_package_spec_char(char c) + { + return (c == '-') || isdigit(c) || (isalpha(c) && islower(c)) || (c == '[') || (c == ']'); + } + + std::string FeatureSpec::to_string() const + { + if (feature().empty()) return spec().to_string(); + return Strings::format("%s[%s]:%s", name(), feature(), triplet()); + } + + std::vector<FeatureSpec> FeatureSpec::from_strings_and_triplet(const std::vector<std::string>& depends, + const Triplet& triplet) + { + std::vector<FeatureSpec> f_specs; + for (auto&& depend : depends) + { + auto maybe_spec = ParsedSpecifier::from_string(depend); + if (auto spec = maybe_spec.get()) + { + Checks::check_exit(VCPKG_LINE_INFO, + spec->triplet.empty(), + "error: triplets cannot currently be specified in this context: %s", + depend); + PackageSpec pspec = + PackageSpec::from_name_and_triplet(spec->name, triplet).value_or_exit(VCPKG_LINE_INFO); + + for (auto&& feature : spec->features) + f_specs.push_back(FeatureSpec{pspec, feature}); + + if (spec->features.empty()) f_specs.push_back(FeatureSpec{pspec, ""}); + } + else + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "error while parsing feature list: %s: %s", + vcpkg::to_string(maybe_spec.error()), + depend); + } + } + return f_specs; + } + + std::vector<FeatureSpec> FullPackageSpec::to_feature_specs(const std::vector<FullPackageSpec>& specs) + { + std::vector<FeatureSpec> ret; + for (auto&& spec : specs) + { + ret.emplace_back(spec.package_spec, ""); + for (auto&& feature : spec.features) + ret.emplace_back(spec.package_spec, feature); + } + return ret; + } + + ExpectedT<FullPackageSpec, PackageSpecParseResult> FullPackageSpec::from_string(const std::string& spec_as_string, + const Triplet& default_triplet) + { + auto res = ParsedSpecifier::from_string(spec_as_string); + if (auto p = res.get()) + { + FullPackageSpec fspec; + Triplet t = p->triplet.empty() ? default_triplet : Triplet::from_canonical_name(p->triplet); + fspec.package_spec = PackageSpec::from_name_and_triplet(p->name, t).value_or_exit(VCPKG_LINE_INFO); + fspec.features = std::move(p->features); + return fspec; + } + return res.error(); + } + + ExpectedT<PackageSpec, PackageSpecParseResult> PackageSpec::from_name_and_triplet(const std::string& name, + const Triplet& triplet) + { + if (Util::find_if_not(name, is_valid_package_spec_char) != name.end()) + { + return PackageSpecParseResult::INVALID_CHARACTERS; + } + + PackageSpec p; + p.m_name = name; + p.m_triplet = triplet; + return p; + } + + std::vector<PackageSpec> PackageSpec::to_package_specs(const std::vector<std::string>& ports, + const Triplet& triplet) + { + return Util::fmap(ports, [&](const std::string& spec_as_string) -> PackageSpec { + auto maybe_spec = PackageSpec::from_name_and_triplet(spec_as_string, triplet); + if (auto spec = maybe_spec.get()) + { + return std::move(*spec); + } + + const PackageSpecParseResult error_type = maybe_spec.error(); + Checks::exit_with_message(VCPKG_LINE_INFO, + "Invalid package: %s\n" + "%s", + spec_as_string, + vcpkg::to_string(error_type)); + }); + } + + const std::string& PackageSpec::name() const { return this->m_name; } + + const Triplet& PackageSpec::triplet() const { return this->m_triplet; } + + std::string PackageSpec::dir() const { return Strings::format("%s_%s", this->m_name, this->m_triplet); } + + std::string PackageSpec::to_string() const { return Strings::format("%s:%s", this->name(), this->triplet()); } + + bool operator==(const PackageSpec& left, const PackageSpec& right) + { + return left.name() == right.name() && left.triplet() == right.triplet(); + } + + bool operator!=(const PackageSpec& left, const PackageSpec& right) { return !(left == right); } + + ExpectedT<ParsedSpecifier, PackageSpecParseResult> ParsedSpecifier::from_string(const std::string& input) + { + auto pos = input.find(':'); + auto pos_l_bracket = input.find('['); + auto pos_r_bracket = input.find(']'); + + ParsedSpecifier f; + if (pos == std::string::npos && pos_l_bracket == std::string::npos) + { + f.name = input; + return f; + } + else if (pos == std::string::npos) + { + if (pos_r_bracket == std::string::npos || pos_l_bracket >= pos_r_bracket) + { + return PackageSpecParseResult::INVALID_CHARACTERS; + } + const std::string name = input.substr(0, pos_l_bracket); + f.name = name; + f.features = parse_comma_list(input.substr(pos_l_bracket + 1, pos_r_bracket - pos_l_bracket - 1)); + return f; + } + else if (pos_l_bracket == std::string::npos && pos_r_bracket == std::string::npos) + { + const std::string name = input.substr(0, pos); + f.triplet = input.substr(pos + 1); + f.name = name; + } + else + { + if (pos_r_bracket == std::string::npos || pos_l_bracket >= pos_r_bracket) + { + return PackageSpecParseResult::INVALID_CHARACTERS; + } + const std::string name = input.substr(0, pos_l_bracket); + f.features = parse_comma_list(input.substr(pos_l_bracket + 1, pos_r_bracket - pos_l_bracket - 1)); + f.triplet = input.substr(pos + 1); + f.name = name; + } + + auto pos2 = input.find(':', pos + 1); + if (pos2 != std::string::npos) + { + return PackageSpecParseResult::TOO_MANY_COLONS; + } + return f; + } + + ExpectedT<Features, PackageSpecParseResult> Features::from_string(const std::string& name) + { + auto maybe_spec = ParsedSpecifier::from_string(name); + if (auto spec = maybe_spec.get()) + { + Checks::check_exit( + VCPKG_LINE_INFO, spec->triplet.empty(), "error: triplet not allowed in specifier: %s", name); + + Features f; + f.name = spec->name; + f.features = spec->features; + return f; + } + + return maybe_spec.error(); + } +} diff --git a/toolsrc/src/vcpkg/packagespecparseresult.cpp b/toolsrc/src/vcpkg/packagespecparseresult.cpp new file mode 100644 index 000000000..b12bd12d0 --- /dev/null +++ b/toolsrc/src/vcpkg/packagespecparseresult.cpp @@ -0,0 +1,21 @@ +#include "pch.h" + +#include <vcpkg/packagespecparseresult.h> + +#include <vcpkg/base/checks.h> + +namespace vcpkg +{ + CStringView to_string(PackageSpecParseResult ev) noexcept + { + switch (ev) + { + case PackageSpecParseResult::SUCCESS: return "OK"; + case PackageSpecParseResult::TOO_MANY_COLONS: return "Too many colons"; + case PackageSpecParseResult::INVALID_CHARACTERS: + return "Contains invalid characters. Only alphanumeric lowercase ASCII characters and dashes are " + "allowed"; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } +} diff --git a/toolsrc/src/ParagraphParseResult.cpp b/toolsrc/src/vcpkg/paragraphparseresult.cpp index 5c8c1d59d..920a4b16b 100644 --- a/toolsrc/src/ParagraphParseResult.cpp +++ b/toolsrc/src/vcpkg/paragraphparseresult.cpp @@ -1,7 +1,7 @@ #include "pch.h" -#include "ParagraphParseResult.h" -#include "vcpkg_Checks.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/paragraphparseresult.h> namespace vcpkg { diff --git a/toolsrc/src/Paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index 70d3db2b3..77c028937 100644 --- a/toolsrc/src/Paragraphs.cpp +++ b/toolsrc/src/vcpkg/paragraphs.cpp @@ -1,8 +1,12 @@ #include "pch.h" -#include "ParagraphParseResult.h" -#include "Paragraphs.h" -#include "vcpkg_Files.h" +#include <vcpkg/base/files.h> +#include <vcpkg/base/util.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/paragraphparseresult.h> +#include <vcpkg/paragraphs.h> + +using namespace vcpkg::Parse; namespace vcpkg::Paragraphs { @@ -168,7 +172,7 @@ namespace vcpkg::Paragraphs return parse_single_paragraph(*spgh); } - return contents.error_code(); + return contents.error(); } Expected<std::vector<std::unordered_map<std::string, std::string>>> get_paragraphs(const Files::Filesystem& fs, @@ -180,7 +184,7 @@ namespace vcpkg::Paragraphs return parse_paragraphs(*spgh); } - return contents.error_code(); + return contents.error(); } Expected<std::unordered_map<std::string, std::string>> parse_single_paragraph(const std::string& str) @@ -201,54 +205,94 @@ namespace vcpkg::Paragraphs return Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); } - Expected<SourceParagraph> try_load_port(const Files::Filesystem& fs, const fs::path& path) + ParseExpected<SourceControlFile> try_load_port(const Files::Filesystem& fs, const fs::path& path) { - Expected<std::unordered_map<std::string, std::string>> pghs = get_single_paragraph(fs, path / "CONTROL"); - if (auto p = pghs.get()) + Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = get_paragraphs(fs, path / "CONTROL"); + if (auto vector_pghs = pghs.get()) { - return SourceParagraph(*p); + auto csf = SourceControlFile::parse_control_file(std::move(*vector_pghs)); + if (!GlobalState::feature_packages) + { + if (auto ptr = csf.get()) + { + Checks::check_exit(VCPKG_LINE_INFO, ptr->get() != nullptr); + ptr->get()->core_paragraph->default_features.clear(); + ptr->get()->feature_paragraphs.clear(); + } + } + return csf; } - - return pghs.error_code(); + auto error_info = std::make_unique<ParseControlErrorInfo>(); + error_info->name = path.filename().generic_u8string(); + error_info->error = pghs.error(); + return error_info; } - Expected<BinaryParagraph> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec) + Expected<BinaryControlFile> try_load_cached_package(const VcpkgPaths& paths, const PackageSpec& spec) { - Expected<std::unordered_map<std::string, std::string>> pghs = - get_single_paragraph(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL"); + Expected<std::vector<std::unordered_map<std::string, std::string>>> pghs = + get_paragraphs(paths.get_filesystem(), paths.package_dir(spec) / "CONTROL"); if (auto p = pghs.get()) { - return BinaryParagraph(*p); + BinaryControlFile bcf; + bcf.core_paragraph = BinaryParagraph(p->front()); + p->erase(p->begin()); + + bcf.features = + Util::fmap(*p, [&](auto&& raw_feature) -> BinaryParagraph { return BinaryParagraph(raw_feature); }); + + return bcf; } - return pghs.error_code(); + return pghs.error(); } - std::vector<SourceParagraph> load_all_ports(const Files::Filesystem& fs, const fs::path& ports_dir) + LoadResults try_load_all_ports(const Files::Filesystem& fs, const fs::path& ports_dir) { - std::vector<SourceParagraph> output; - for (auto&& path : fs.get_files_non_recursive(ports_dir)) + LoadResults ret; + auto port_dirs = fs.get_files_non_recursive(ports_dir); + Util::sort(port_dirs); + Util::erase_remove_if(port_dirs, [&](auto&& port_dir_entry) { + return fs.is_regular_file(port_dir_entry) && port_dir_entry.filename() == ".DS_Store"; + }); + + for (auto&& path : port_dirs) { - Expected<SourceParagraph> source_paragraph = try_load_port(fs, path); - if (auto srcpgh = source_paragraph.get()) + auto maybe_spgh = try_load_port(fs, path); + if (const auto spgh = maybe_spgh.get()) { - output.emplace_back(std::move(*srcpgh)); + ret.paragraphs.emplace_back(std::move(*spgh)); + } + else + { + ret.errors.emplace_back(std::move(maybe_spgh).error()); } } - - return output; + return ret; } - std::map<std::string, VersionT> extract_port_names_and_versions( - const std::vector<SourceParagraph>& source_paragraphs) + std::vector<std::unique_ptr<SourceControlFile>> load_all_ports(const Files::Filesystem& fs, + const fs::path& ports_dir) { - std::map<std::string, VersionT> names_and_versions; - for (const SourceParagraph& port : source_paragraphs) + auto results = try_load_all_ports(fs, ports_dir); + if (!results.errors.empty()) { - names_and_versions.emplace(port.name, port.version); + if (GlobalState::debugging) + { + print_error_message(results.errors); + } + else + { + for (auto&& error : results.errors) + { + System::println( + System::Color::warning, "Warning: an error occurred while parsing '%s'", error->name); + } + System::println(System::Color::warning, + "Use '--debug' to get more information about the parse failures.\n"); + } } - - return names_and_versions; + return std::move(results.paragraphs); } } diff --git a/toolsrc/src/vcpkg/parse.cpp b/toolsrc/src/vcpkg/parse.cpp new file mode 100644 index 000000000..d50296cf8 --- /dev/null +++ b/toolsrc/src/vcpkg/parse.cpp @@ -0,0 +1,80 @@ +#include "pch.h" + +#include <vcpkg/parse.h> + +#include <vcpkg/base/util.h> + +namespace vcpkg::Parse +{ + static Optional<std::string> remove_field(std::unordered_map<std::string, std::string>* fields, + const std::string& fieldname) + { + auto it = fields->find(fieldname); + if (it == fields->end()) + { + return nullopt; + } + + const std::string value = std::move(it->second); + fields->erase(it); + return value; + } + + void ParagraphParser::required_field(const std::string& fieldname, std::string& out) + { + auto maybe_field = remove_field(&fields, fieldname); + if (const auto field = maybe_field.get()) + out = std::move(*field); + else + missing_fields.push_back(fieldname); + } + std::string ParagraphParser::optional_field(const std::string& fieldname) const + { + return remove_field(&fields, fieldname).value_or(""); + } + std::unique_ptr<ParseControlErrorInfo> ParagraphParser::error_info(const std::string& name) const + { + if (!fields.empty() || !missing_fields.empty()) + { + auto err = std::make_unique<ParseControlErrorInfo>(); + err->name = name; + err->extra_fields = Util::extract_keys(fields); + err->missing_fields = std::move(missing_fields); + return err; + } + return nullptr; + } + + std::vector<std::string> parse_comma_list(const std::string& str) + { + if (str.empty()) + { + return {}; + } + + std::vector<std::string> out; + + size_t cur = 0; + do + { + auto pos = str.find(',', cur); + if (pos == std::string::npos) + { + out.push_back(str.substr(cur)); + break; + } + out.push_back(str.substr(cur, pos - cur)); + + // skip comma and space + ++pos; + if (str[pos] == ' ') + { + ++pos; + } + + cur = pos; + } while (cur != std::string::npos); + + return out; + } +} diff --git a/toolsrc/src/PostBuildLint_BuildType.cpp b/toolsrc/src/vcpkg/postbuildlint.buildtype.cpp index e690036d2..e966ce78a 100644 --- a/toolsrc/src/PostBuildLint_BuildType.cpp +++ b/toolsrc/src/vcpkg/postbuildlint.buildtype.cpp @@ -1,28 +1,30 @@ #include "pch.h" -#include "PostBuildLint_BuildType.h" -#include "vcpkg_Checks.h" +#include <vcpkg/base/checks.h> +#include <vcpkg/postbuildlint.buildtype.h> + +using vcpkg::Build::ConfigurationType; namespace vcpkg::PostBuildLint { - BuildType BuildType::value_of(const ConfigurationType& config, const LinkageType& linkage) + BuildType BuildType::value_of(const ConfigurationType& config, const Build::LinkageType& linkage) { - if (config == ConfigurationTypeC::DEBUG && linkage == LinkageTypeC::STATIC) + if (config == ConfigurationType::DEBUG && linkage == Build::LinkageType::STATIC) { return BuildTypeC::DEBUG_STATIC; } - if (config == ConfigurationTypeC::DEBUG && linkage == LinkageTypeC::DYNAMIC) + if (config == ConfigurationType::DEBUG && linkage == Build::LinkageType::DYNAMIC) { return BuildTypeC::DEBUG_DYNAMIC; } - if (config == ConfigurationTypeC::RELEASE && linkage == LinkageTypeC::STATIC) + if (config == ConfigurationType::RELEASE && linkage == Build::LinkageType::STATIC) { return BuildTypeC::RELEASE_STATIC; } - if (config == ConfigurationTypeC::RELEASE && linkage == LinkageTypeC::DYNAMIC) + if (config == ConfigurationType::RELEASE && linkage == Build::LinkageType::DYNAMIC) { return BuildTypeC::RELEASE_DYNAMIC; } @@ -32,7 +34,7 @@ namespace vcpkg::PostBuildLint const ConfigurationType& BuildType::config() const { return this->m_config; } - const LinkageType& BuildType::linkage() const { return this->m_linkage; } + const Build::LinkageType& BuildType::linkage() const { return this->m_linkage; } const std::regex& BuildType::crt_regex() const { diff --git a/toolsrc/src/PostBuildLint.cpp b/toolsrc/src/vcpkg/postbuildlint.cpp index 0d2f556c1..6fe11951f 100644 --- a/toolsrc/src/PostBuildLint.cpp +++ b/toolsrc/src/vcpkg/postbuildlint.cpp @@ -1,17 +1,18 @@ #include "pch.h" -#include "PackageSpec.h" -#include "PostBuildLint.h" -#include "PostBuildLint_BuildType.h" -#include "VcpkgPaths.h" -#include "coff_file_reader.h" -#include "vcpkg_Build.h" -#include "vcpkg_Files.h" -#include "vcpkg_System.h" -#include "vcpkg_Util.h" +#include <vcpkg/base/cofffilereader.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/build.h> +#include <vcpkg/packagespec.h> +#include <vcpkg/postbuildlint.buildtype.h> +#include <vcpkg/postbuildlint.h> +#include <vcpkg/vcpkgpaths.h> -using vcpkg::Build::PreBuildInfo; using vcpkg::Build::BuildInfo; +using vcpkg::Build::BuildPolicy; +using vcpkg::Build::PreBuildInfo; namespace vcpkg::PostBuildLint { @@ -37,44 +38,49 @@ namespace vcpkg::PostBuildLint } }; - const std::vector<OutdatedDynamicCrt>& get_outdated_dynamic_crts() - { - static const std::vector<OutdatedDynamicCrt> v = {{"msvcp100.dll", R"(msvcp100\.dll)"}, - {"msvcp100d.dll", R"(msvcp100d\.dll)"}, - {"msvcp110.dll", R"(msvcp110\.dll)"}, - {"msvcp110_win.dll", R"(msvcp110_win\.dll)"}, - {"msvcp120.dll", R"(msvcp120\.dll)"}, - {"msvcp120_clr0400.dll", R"(msvcp120_clr0400\.dll)"}, - {"msvcp60.dll", R"(msvcp60\.dll)"}, - {"msvcp60.dll", R"(msvcp60\.dll)"}, - - {"msvcr100.dll", R"(msvcr100\.dll)"}, - {"msvcr100d.dll", R"(msvcr100d\.dll)"}, - {"msvcr100_clr0400.dll", R"(msvcr100_clr0400\.dll)"}, - {"msvcr110.dll", R"(msvcr110\.dll)"}, - {"msvcr120.dll", R"(msvcr120\.dll)"}, - {"msvcr120_clr0400.dll", R"(msvcr120_clr0400\.dll)"}, - {"msvcrt.dll", R"(msvcrt\.dll)"}, - {"msvcrt20.dll", R"(msvcrt20\.dll)"}, - {"msvcrt40.dll", R"(msvcrt40\.dll)"}}; - - return v; - } + Span<const OutdatedDynamicCrt> get_outdated_dynamic_crts(const Optional<std::string>& toolset_version) + { + static const std::vector<OutdatedDynamicCrt> V_NO_120 = { + {"msvcp100.dll", R"(msvcp100\.dll)"}, + {"msvcp100d.dll", R"(msvcp100d\.dll)"}, + {"msvcp110.dll", R"(msvcp110\.dll)"}, + {"msvcp110_win.dll", R"(msvcp110_win\.dll)"}, + {"msvcp60.dll", R"(msvcp60\.dll)"}, + {"msvcp60.dll", R"(msvcp60\.dll)"}, + + {"msvcrt.dll", R"(msvcrt\.dll)"}, + {"msvcr100.dll", R"(msvcr100\.dll)"}, + {"msvcr100d.dll", R"(msvcr100d\.dll)"}, + {"msvcr100_clr0400.dll", R"(msvcr100_clr0400\.dll)"}, + {"msvcr110.dll", R"(msvcr110\.dll)"}, + {"msvcrt20.dll", R"(msvcrt20\.dll)"}, + {"msvcrt40.dll", R"(msvcrt40\.dll)"}, + }; - template<class T> - static bool contains_and_enabled(const std::map<T, bool> map, const T& key) - { - auto it = map.find(key); - if (it != map.cend()) return it->second; + static const std::vector<OutdatedDynamicCrt> V_NO_MSVCRT = [&]() { + auto ret = V_NO_120; + ret.push_back({"msvcp120.dll", R"(msvcp120\.dll)"}); + ret.push_back({"msvcp120_clr0400.dll", R"(msvcp120_clr0400\.dll)"}); + ret.push_back({"msvcr120.dll", R"(msvcr120\.dll)"}); + ret.push_back({"msvcr120_clr0400.dll", R"(msvcr120_clr0400\.dll)"}); + return ret; + }(); + + const auto tsv = toolset_version.get(); + if (tsv && (*tsv) == "v120") + { + return V_NO_120; + } - return false; + // Default case for all version >= VS 2015. + return V_NO_MSVCRT; } static LintStatus check_for_files_in_include_directory(const Files::Filesystem& fs, - const std::map<BuildPolicies, bool>& policies, + const Build::BuildPolicies& policies, const fs::path& package_dir) { - if (contains_and_enabled(policies, BuildPoliciesC::EMPTY_INCLUDE_FOLDER)) + if (policies.is_enabled(BuildPolicy::EMPTY_INCLUDE_FOLDER)) { return LintStatus::SUCCESS; } @@ -82,8 +88,9 @@ namespace vcpkg::PostBuildLint const fs::path include_dir = package_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."); + System::println( + System::Color::warning, + "The folder /include is empty or not present. This indicates the library was not correctly installed."); return LintStatus::ERROR_DETECTED; } @@ -292,13 +299,10 @@ namespace vcpkg::PostBuildLint std::vector<fs::path> dlls_with_no_exports; for (const fs::path& dll : dlls) { - const std::wstring cmd_line = - Strings::wformat(LR"("%s" /exports "%s")", dumpbin_exe.native(), dll.native()); + const std::string cmd_line = + Strings::format(R"("%s" /exports "%s")", dumpbin_exe.u8string(), dll.u8string()); System::ExitCodeAndOutput ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, - ec_data.exit_code == 0, - "Running command:\n %s\n failed", - Strings::to_utf8(cmd_line)); + Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", cmd_line); if (ec_data.output.find("ordinal hint RVA name") == std::string::npos) { @@ -329,13 +333,10 @@ namespace vcpkg::PostBuildLint std::vector<fs::path> dlls_with_improper_uwp_bit; for (const fs::path& dll : dlls) { - const std::wstring cmd_line = - Strings::wformat(LR"("%s" /headers "%s")", dumpbin_exe.native(), dll.native()); + const std::string cmd_line = + Strings::format(R"("%s" /headers "%s")", dumpbin_exe.u8string(), dll.u8string()); System::ExitCodeAndOutput ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, - ec_data.exit_code == 0, - "Running command:\n %s\n failed", - Strings::to_utf8(cmd_line)); + Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", cmd_line); if (ec_data.output.find("App Container") == std::string::npos) { @@ -360,6 +361,7 @@ namespace vcpkg::PostBuildLint std::string actual_arch; }; +#if defined(_WIN32) static std::string get_actual_architecture(const MachineType& machine_type) { switch (machine_type) @@ -369,20 +371,23 @@ namespace vcpkg::PostBuildLint case MachineType::I386: return "x86"; case MachineType::ARM: case MachineType::ARMNT: return "arm"; + case MachineType::ARM64: return "arm64"; default: return "Machine Type Code = " + std::to_string(static_cast<uint16_t>(machine_type)); } } +#endif +#if defined(_WIN32) static void print_invalid_architecture_files(const std::string& expected_architecture, std::vector<FileAndArch> binaries_with_invalid_architecture) { System::println(System::Color::warning, "The following files were built for an incorrect architecture:"); - System::println(""); + System::println(); for (const FileAndArch& b : binaries_with_invalid_architecture) { System::println(" %s", b.file.generic_string()); System::println("Expected %s, but was: %s", expected_architecture, b.actual_arch); - System::println(""); + System::println(); } } @@ -397,7 +402,7 @@ namespace vcpkg::PostBuildLint file.extension() == ".dll", "The file extension was not .dll: %s", file.generic_string()); - COFFFileReader::DllInfo info = COFFFileReader::read_dll(file); + const CoffFileReader::DllInfo info = CoffFileReader::read_dll(file); const std::string actual_architecture = get_actual_architecture(info.machine_type); if (expected_architecture != actual_architecture) @@ -414,10 +419,12 @@ namespace vcpkg::PostBuildLint return LintStatus::SUCCESS; } +#endif static LintStatus check_lib_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) { +#if defined(_WIN32) std::vector<FileAndArch> binaries_with_invalid_architecture; for (const fs::path& file : files) @@ -426,7 +433,7 @@ namespace vcpkg::PostBuildLint file.extension() == ".lib", "The file extension was not .lib: %s", file.generic_string()); - COFFFileReader::LibInfo info = COFFFileReader::read_lib(file); + CoffFileReader::LibInfo info = CoffFileReader::read_lib(file); // This is zero for folly's debug library // TODO: Why? @@ -449,6 +456,7 @@ namespace vcpkg::PostBuildLint print_invalid_architecture_files(expected_architecture, binaries_with_invalid_architecture); return LintStatus::ERROR_DETECTED; } +#endif return LintStatus::SUCCESS; } @@ -477,7 +485,7 @@ namespace vcpkg::PostBuildLint } System::println(System::Color::warning, - "Mismatching number of debug and release binaries. Found %d for debug but %d for release.", + "Mismatching number of debug and release binaries. Found %zd for debug but %zd for release.", debug_count, release_count); System::println("Debug binaries"); @@ -495,21 +503,17 @@ namespace vcpkg::PostBuildLint System::println(System::Color::warning, "Release binaries were not found"); } - System::println(""); + System::println(); return LintStatus::ERROR_DETECTED; } - static LintStatus check_lib_files_are_available_if_dlls_are_available(const std::map<BuildPolicies, bool>& policies, + static LintStatus check_lib_files_are_available_if_dlls_are_available(const Build::BuildPolicies& policies, const size_t lib_count, const size_t dll_count, const fs::path& lib_dir) { - auto it = policies.find(BuildPoliciesC::DLLS_WITHOUT_LIBS); - if (it != policies.cend() && it->second) - { - return LintStatus::SUCCESS; - } + if (policies.is_enabled(BuildPolicy::DLLS_WITHOUT_LIBS)) return LintStatus::SUCCESS; if (lib_count == 0 && dll_count != 0) { @@ -517,7 +521,7 @@ namespace vcpkg::PostBuildLint System::println(System::Color::warning, "If this is intended, add the following line in the portfile:\n" " SET(%s enabled)", - BuildPoliciesC::DLLS_WITHOUT_LIBS.cmake_variable()); + to_cmake_variable(BuildPolicy::DLLS_WITHOUT_LIBS)); return LintStatus::ERROR_DETECTED; } @@ -592,7 +596,7 @@ namespace vcpkg::PostBuildLint return LintStatus::SUCCESS; } - struct BuildType_and_file + struct BuildTypeAndFile { fs::path file; BuildType build_type; @@ -606,17 +610,14 @@ namespace vcpkg::PostBuildLint bad_build_types.erase(std::remove(bad_build_types.begin(), bad_build_types.end(), expected_build_type), bad_build_types.end()); - std::vector<BuildType_and_file> libs_with_invalid_crt; + std::vector<BuildTypeAndFile> libs_with_invalid_crt; for (const fs::path& lib : libs) { - const std::wstring cmd_line = - Strings::wformat(LR"("%s" /directives "%s")", dumpbin_exe.native(), lib.native()); + const std::string cmd_line = + Strings::format(R"("%s" /directives "%s")", dumpbin_exe.u8string(), lib.u8string()); System::ExitCodeAndOutput ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, - ec_data.exit_code == 0, - "Running command:\n %s\n failed", - Strings::to_utf8(cmd_line)); + Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", cmd_line); for (const BuildType& bad_build_type : bad_build_types) { @@ -633,12 +634,12 @@ namespace vcpkg::PostBuildLint System::println(System::Color::warning, "Expected %s crt linkage, but the following libs had invalid crt linkage:", expected_build_type.to_string()); - System::println(""); - for (const BuildType_and_file btf : libs_with_invalid_crt) + System::println(); + for (const BuildTypeAndFile btf : libs_with_invalid_crt) { System::println(" %s: %s", btf.file.generic_string(), btf.build_type.to_string()); } - System::println(""); + System::println(); System::println(System::Color::warning, "To inspect the lib files, use:\n dumpbin.exe /directives mylibfile.lib"); @@ -648,31 +649,30 @@ namespace vcpkg::PostBuildLint return LintStatus::SUCCESS; } - struct OutdatedDynamicCrt_and_file + struct OutdatedDynamicCrtAndFile { fs::path file; OutdatedDynamicCrt outdated_crt; - OutdatedDynamicCrt_and_file() = delete; + OutdatedDynamicCrtAndFile() = delete; }; - static LintStatus check_outdated_crt_linkage_of_dlls(const std::vector<fs::path>& dlls, const fs::path dumpbin_exe) + static LintStatus check_outdated_crt_linkage_of_dlls(const std::vector<fs::path>& dlls, + const fs::path dumpbin_exe, + const BuildInfo& build_info, + const PreBuildInfo& pre_build_info) { - const std::vector<OutdatedDynamicCrt>& outdated_crts = get_outdated_dynamic_crts(); + if (build_info.policies.is_enabled(BuildPolicy::ALLOW_OBSOLETE_MSVCRT)) return LintStatus::SUCCESS; - std::vector<OutdatedDynamicCrt_and_file> dlls_with_outdated_crt; + std::vector<OutdatedDynamicCrtAndFile> dlls_with_outdated_crt; for (const fs::path& dll : dlls) { - const std::wstring cmd_line = - Strings::wformat(LR"("%s" /dependents "%s")", dumpbin_exe.native(), dll.native()); + const auto cmd_line = Strings::format(R"("%s" /dependents "%s")", dumpbin_exe.u8string(), dll.u8string()); System::ExitCodeAndOutput ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(VCPKG_LINE_INFO, - ec_data.exit_code == 0, - "Running command:\n %s\n failed", - Strings::to_utf8(cmd_line)); + Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0, "Running command:\n %s\n failed", cmd_line); - for (const OutdatedDynamicCrt& outdated_crt : outdated_crts) + for (const OutdatedDynamicCrt& outdated_crt : get_outdated_dynamic_crts(pre_build_info.platform_toolset)) { if (std::regex_search(ec_data.output.cbegin(), ec_data.output.cend(), outdated_crt.regex)) { @@ -685,12 +685,12 @@ namespace vcpkg::PostBuildLint if (!dlls_with_outdated_crt.empty()) { System::println(System::Color::warning, "Detected outdated dynamic CRT in the following files:"); - System::println(""); - for (const OutdatedDynamicCrt_and_file btf : dlls_with_outdated_crt) + System::println(); + for (const OutdatedDynamicCrtAndFile btf : dlls_with_outdated_crt) { System::println(" %s: %s", btf.file.generic_string(), btf.outdated_crt.name); } - System::println(""); + System::println(); System::println(System::Color::warning, "To inspect the dll files, use:\n dumpbin.exe /dependents mydllfile.dll"); @@ -705,8 +705,8 @@ namespace vcpkg::PostBuildLint std::vector<fs::path> misplaced_files = fs.get_files_non_recursive(dir); Util::unstable_keep_if(misplaced_files, [&fs](const fs::path& path) { const std::string filename = path.filename().generic_string(); - if (Strings::case_insensitive_ascii_compare(filename.c_str(), "CONTROL") == 0 || - Strings::case_insensitive_ascii_compare(filename.c_str(), "BUILD_INFO") == 0) + if (Strings::case_insensitive_ascii_equals(filename.c_str(), "CONTROL") || + Strings::case_insensitive_ascii_equals(filename.c_str(), "BUILD_INFO")) return false; return !fs.is_directory(path); }); @@ -732,12 +732,12 @@ namespace vcpkg::PostBuildLint const auto& fs = paths.get_filesystem(); // for dumpbin - const Toolset& toolset = paths.get_toolset(); + const Toolset& toolset = paths.get_toolset(pre_build_info); const fs::path package_dir = paths.package_dir(spec); size_t error_count = 0; - if (contains_and_enabled(build_info.policies, BuildPoliciesC::EMPTY_PACKAGE)) + if (build_info.policies.is_enabled(BuildPolicy::EMPTY_PACKAGE)) { return error_count; } @@ -764,7 +764,8 @@ namespace vcpkg::PostBuildLint std::vector<fs::path> release_libs = fs.get_files_recursive(release_lib_dir); Util::unstable_keep_if(release_libs, has_extension_pred(fs, ".lib")); - error_count += check_matching_debug_and_release_binaries(debug_libs, release_libs); + if (!pre_build_info.build_type) + error_count += check_matching_debug_and_release_binaries(debug_libs, release_libs); { std::vector<fs::path> libs; @@ -774,16 +775,17 @@ namespace vcpkg::PostBuildLint error_count += check_lib_architecture(pre_build_info.target_architecture, libs); } + std::vector<fs::path> debug_dlls = fs.get_files_recursive(debug_bin_dir); + Util::unstable_keep_if(debug_dlls, has_extension_pred(fs, ".dll")); + std::vector<fs::path> release_dlls = fs.get_files_recursive(release_bin_dir); + Util::unstable_keep_if(release_dlls, has_extension_pred(fs, ".dll")); + switch (build_info.library_linkage) { - case LinkageType::BackingEnum::DYNAMIC: + case Build::LinkageType::DYNAMIC: { - std::vector<fs::path> debug_dlls = fs.get_files_recursive(debug_bin_dir); - Util::unstable_keep_if(debug_dlls, has_extension_pred(fs, ".dll")); - std::vector<fs::path> release_dlls = fs.get_files_recursive(release_bin_dir); - Util::unstable_keep_if(release_dlls, has_extension_pred(fs, ".dll")); - - error_count += check_matching_debug_and_release_binaries(debug_dlls, release_dlls); + if (!pre_build_info.build_type) + error_count += check_matching_debug_and_release_binaries(debug_dlls, release_dlls); error_count += check_lib_files_are_available_if_dlls_are_available( build_info.policies, debug_libs.size(), debug_dlls.size(), debug_lib_dir); @@ -794,35 +796,43 @@ namespace vcpkg::PostBuildLint dlls.insert(dlls.cend(), debug_dlls.cbegin(), debug_dlls.cend()); dlls.insert(dlls.cend(), release_dlls.cbegin(), release_dlls.cend()); - error_count += check_exports_of_dlls(dlls, toolset.dumpbin); - error_count += check_uwp_bit_of_dlls(pre_build_info.cmake_system_name, dlls, toolset.dumpbin); - error_count += check_dll_architecture(pre_build_info.target_architecture, dlls); + if (!toolset.dumpbin.empty()) + { + error_count += check_exports_of_dlls(dlls, toolset.dumpbin); + error_count += check_uwp_bit_of_dlls(pre_build_info.cmake_system_name, dlls, toolset.dumpbin); + error_count += + check_outdated_crt_linkage_of_dlls(dlls, toolset.dumpbin, build_info, pre_build_info); + } - error_count += check_outdated_crt_linkage_of_dlls(dlls, toolset.dumpbin); +#if defined(_WIN32) + error_count += check_dll_architecture(pre_build_info.target_architecture, dlls); +#endif break; } - case LinkageType::BackingEnum::STATIC: + case Build::LinkageType::STATIC: { - std::vector<fs::path> dlls = fs.get_files_recursive(package_dir); - Util::unstable_keep_if(dlls, has_extension_pred(fs, ".dll")); + auto dlls = release_dlls; + dlls.insert(dlls.end(), debug_dlls.begin(), debug_dlls.end()); error_count += check_no_dlls_present(dlls); error_count += check_bin_folders_are_not_present_in_static_build(fs, package_dir); - if (!contains_and_enabled(build_info.policies, BuildPoliciesC::ONLY_RELEASE_CRT)) + if (!toolset.dumpbin.empty()) { + if (!build_info.policies.is_enabled(BuildPolicy::ONLY_RELEASE_CRT)) + { + error_count += check_crt_linkage_of_libs( + BuildType::value_of(Build::ConfigurationType::DEBUG, build_info.crt_linkage), + debug_libs, + toolset.dumpbin); + } error_count += check_crt_linkage_of_libs( - BuildType::value_of(ConfigurationTypeC::DEBUG, build_info.crt_linkage), - debug_libs, + BuildType::value_of(Build::ConfigurationType::RELEASE, build_info.crt_linkage), + release_libs, toolset.dumpbin); } - error_count += - check_crt_linkage_of_libs(BuildType::value_of(ConfigurationTypeC::RELEASE, build_info.crt_linkage), - release_libs, - toolset.dumpbin); break; } - case LinkageType::BackingEnum::NULLVALUE: default: Checks::unreachable(VCPKG_LINE_INFO); } diff --git a/toolsrc/src/vcpkg/remove.cpp b/toolsrc/src/vcpkg/remove.cpp new file mode 100644 index 000000000..13cc9325e --- /dev/null +++ b/toolsrc/src/vcpkg/remove.cpp @@ -0,0 +1,306 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/commands.h> +#include <vcpkg/dependencies.h> +#include <vcpkg/help.h> +#include <vcpkg/input.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/remove.h> +#include <vcpkg/update.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Remove +{ + using Dependencies::RemovePlanAction; + using Dependencies::RemovePlanType; + using Dependencies::RequestType; + using Update::OutdatedPackage; + + void remove_package(const VcpkgPaths& paths, const PackageSpec& spec, StatusParagraphs* status_db) + { + auto& fs = paths.get_filesystem(); + auto maybe_ipv = status_db->find_all_installed(spec); + + Checks::check_exit( + VCPKG_LINE_INFO, maybe_ipv.has_value(), "unable to remove package %s: already removed", spec); + + auto&& ipv = maybe_ipv.value_or_exit(VCPKG_LINE_INFO); + + std::vector<StatusParagraph> spghs; + spghs.emplace_back(*ipv.core); + for (auto&& feature : ipv.features) + { + spghs.emplace_back(*feature); + } + + for (auto&& spgh : spghs) + { + spgh.want = Want::PURGE; + spgh.state = InstallState::HALF_INSTALLED; + write_update(paths, spgh); + } + + auto maybe_lines = fs.read_lines(paths.listfile_path(ipv.core->package)); + + if (const auto lines = maybe_lines.get()) + { + std::vector<fs::path> dirs_touched; + for (auto&& suffix : *lines) + { + if (!suffix.empty() && suffix.back() == '\r') suffix.pop_back(); + + std::error_code ec; + + auto target = paths.installed / suffix; + + const auto status = fs.status(target, ec); + if (ec) + { + System::println(System::Color::error, "failed: status(%s): %s", target.u8string(), 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) + { +#if defined(_WIN32) + fs::stdfs::permissions(target, fs::stdfs::perms::owner_all | fs::stdfs::perms::group_all, ec); + fs.remove(target, ec); + if (ec) + { + System::println( + System::Color::error, "failed: remove(%s): %s", target.u8string(), ec.message()); + } +#else + System::println( + System::Color::error, "failed: remove(%s): %s", target.u8string(), ec.message()); +#endif + } + } + else if (!fs::stdfs::exists(status)) + { + System::println(System::Color::warning, "Warning: %s: file not found", target.u8string()); + } + else + { + System::println(System::Color::warning, "Warning: %s: cannot handle file type", target.u8string()); + } + } + + auto b = dirs_touched.rbegin(); + const auto e = dirs_touched.rend(); + for (; b != e; ++b) + { + if (fs.is_empty(*b)) + { + std::error_code ec; + fs.remove(*b, ec); + if (ec) + { + System::println(System::Color::error, "failed: %s", ec.message()); + } + } + } + + fs.remove(paths.listfile_path(ipv.core->package)); + } + + for (auto&& spgh : spghs) + { + spgh.state = InstallState::NOT_INSTALLED; + write_update(paths, spgh); + + status_db->insert(std::make_unique<StatusParagraph>(std::move(spgh))); + } + } + + static void print_plan(const std::map<RemovePlanType, std::vector<const RemovePlanAction*>>& group_by_plan_type) + { + static constexpr std::array<RemovePlanType, 2> ORDER = {RemovePlanType::NOT_INSTALLED, RemovePlanType::REMOVE}; + + for (const RemovePlanType plan_type : ORDER) + { + const auto it = group_by_plan_type.find(plan_type); + if (it == group_by_plan_type.cend()) + { + continue; + } + + std::vector<const RemovePlanAction*> cont = it->second; + std::sort(cont.begin(), cont.end(), &RemovePlanAction::compare_by_name); + const std::string as_string = Strings::join("\n", cont, [](const RemovePlanAction* p) { + return Dependencies::to_output_string(p->request_type, p->spec.to_string()); + }); + + switch (plan_type) + { + case RemovePlanType::NOT_INSTALLED: + System::println("The following packages are not installed, so not removed:\n%s", as_string); + continue; + case RemovePlanType::REMOVE: + System::println("The following packages will be removed:\n%s", as_string); + continue; + default: Checks::unreachable(VCPKG_LINE_INFO); + } + } + } + + void perform_remove_plan_action(const VcpkgPaths& paths, + const RemovePlanAction& action, + const Purge purge, + StatusParagraphs* status_db) + { + const std::string display_name = action.spec.to_string(); + + switch (action.plan_type) + { + case RemovePlanType::NOT_INSTALLED: + System::println(System::Color::success, "Package %s is not installed", display_name); + break; + case RemovePlanType::REMOVE: + System::println("Removing package %s... ", display_name); + remove_package(paths, action.spec, status_db); + System::println(System::Color::success, "Removing package %s... done", display_name); + break; + case RemovePlanType::UNKNOWN: + default: Checks::unreachable(VCPKG_LINE_INFO); + } + + if (purge == Purge::YES) + { + System::println("Purging package %s... ", display_name); + Files::Filesystem& fs = paths.get_filesystem(); + std::error_code ec; + fs.remove_all(paths.packages / action.spec.dir(), ec); + System::println(System::Color::success, "Purging package %s... done", display_name); + } + } + + static constexpr StringLiteral OPTION_PURGE = "--purge"; + static constexpr StringLiteral OPTION_NO_PURGE = "--no-purge"; + static constexpr StringLiteral OPTION_RECURSE = "--recurse"; + static constexpr StringLiteral OPTION_DRY_RUN = "--dry-run"; + static constexpr StringLiteral OPTION_OUTDATED = "--outdated"; + + static constexpr std::array<CommandSwitch, 5> SWITCHES = {{ + {OPTION_PURGE, "Remove the cached copy of the package (default)"}, + {OPTION_NO_PURGE, "Do not remove the cached copy of the package"}, + {OPTION_RECURSE, "Allow removal of packages not explicitly specified on the command line"}, + {OPTION_DRY_RUN, "Print the packages to be removed, but do not remove them"}, + {OPTION_OUTDATED, "Select all packages with versions that do not match the portfiles"}, + }}; + + static std::vector<std::string> valid_arguments(const VcpkgPaths& paths) + { + const StatusParagraphs status_db = database_load_check(paths); + auto installed_packages = get_installed_ports(status_db); + + return Util::fmap(installed_packages, [](auto&& pgh) -> std::string { return pgh.spec().to_string(); }); + } + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("remove zlib zlib:x64-windows curl boost"), + 0, + SIZE_MAX, + {SWITCHES, {}}, + &valid_arguments, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths, const Triplet& default_triplet) + { + const ParsedArguments options = args.parse_arguments(COMMAND_STRUCTURE); + + StatusParagraphs status_db = database_load_check(paths); + std::vector<PackageSpec> specs; + if (Util::Sets::contains(options.switches, OPTION_OUTDATED)) + { + if (args.command_arguments.size() != 0) + { + System::println(System::Color::error, "Error: 'remove' accepts either libraries or '--outdated'"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + Dependencies::PathsPortFileProvider provider(paths); + + specs = Util::fmap(Update::find_outdated_packages(provider, status_db), + [](auto&& outdated) { return outdated.spec; }); + + if (specs.empty()) + { + System::println(System::Color::success, "There are no outdated packages."); + Checks::exit_success(VCPKG_LINE_INFO); + } + } + else + { + if (args.command_arguments.size() < 1) + { + System::println(System::Color::error, "Error: 'remove' accepts either libraries or '--outdated'"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + specs = Util::fmap(args.command_arguments, [&](auto&& arg) { + return Input::check_and_get_package_spec(arg, default_triplet, COMMAND_STRUCTURE.example_text); + }); + + for (auto&& spec : specs) + Input::check_triplet(spec.triplet(), paths); + } + + const bool no_purge_was_passed = Util::Sets::contains(options.switches, OPTION_NO_PURGE); + const bool purge_was_passed = Util::Sets::contains(options.switches, OPTION_PURGE); + if (purge_was_passed && no_purge_was_passed) + { + System::println(System::Color::error, "Error: cannot specify both --no-purge and --purge."); + System::print(COMMAND_STRUCTURE.example_text); + Checks::exit_fail(VCPKG_LINE_INFO); + } + const Purge purge = to_purge(purge_was_passed || !no_purge_was_passed); + const bool is_recursive = Util::Sets::contains(options.switches, OPTION_RECURSE); + const bool dry_run = Util::Sets::contains(options.switches, OPTION_DRY_RUN); + + const std::vector<RemovePlanAction> remove_plan = Dependencies::create_remove_plan(specs, status_db); + Checks::check_exit(VCPKG_LINE_INFO, !remove_plan.empty(), "Remove plan cannot be empty"); + + std::map<RemovePlanType, std::vector<const RemovePlanAction*>> group_by_plan_type; + Util::group_by(remove_plan, &group_by_plan_type, [](const RemovePlanAction& p) { return p.plan_type; }); + print_plan(group_by_plan_type); + + const bool has_non_user_requested_packages = + Util::find_if(remove_plan, [](const RemovePlanAction& package) -> bool { + return package.request_type != RequestType::USER_REQUESTED; + }) != remove_plan.cend(); + + if (has_non_user_requested_packages) + { + System::println(System::Color::warning, + "Additional packages (*) need to be removed to complete this operation."); + + if (!is_recursive) + { + System::println(System::Color::warning, + "If you are sure you want to remove them, run the command with the --recurse option"); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + + if (dry_run) + { + Checks::exit_success(VCPKG_LINE_INFO); + } + + for (const RemovePlanAction& action : remove_plan) + { + perform_remove_plan_action(paths, action, purge, &status_db); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/sourceparagraph.cpp b/toolsrc/src/vcpkg/sourceparagraph.cpp new file mode 100644 index 000000000..ed61cb42a --- /dev/null +++ b/toolsrc/src/vcpkg/sourceparagraph.cpp @@ -0,0 +1,282 @@ +#include "pch.h" + +#include <vcpkg/packagespec.h> +#include <vcpkg/sourceparagraph.h> +#include <vcpkg/triplet.h> + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/expected.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> + +namespace vcpkg +{ + using namespace vcpkg::Parse; + + namespace SourceParagraphFields + { + static const std::string BUILD_DEPENDS = "Build-Depends"; + static const std::string DEFAULTFEATURES = "Default-Features"; + static const std::string DESCRIPTION = "Description"; + static const std::string FEATURE = "Feature"; + static const std::string MAINTAINER = "Maintainer"; + static const std::string SOURCE = "Source"; + static const std::string SUPPORTS = "Supports"; + static const std::string VERSION = "Version"; + } + + static Span<const std::string> get_list_of_valid_fields() + { + static const std::string valid_fields[] = { + SourceParagraphFields::SOURCE, + SourceParagraphFields::VERSION, + SourceParagraphFields::DESCRIPTION, + SourceParagraphFields::MAINTAINER, + SourceParagraphFields::BUILD_DEPENDS, + }; + + return valid_fields; + } + + void print_error_message(Span<const std::unique_ptr<Parse::ParseControlErrorInfo>> error_info_list) + { + Checks::check_exit(VCPKG_LINE_INFO, error_info_list.size() > 0); + + for (auto&& error_info : error_info_list) + { + Checks::check_exit(VCPKG_LINE_INFO, error_info != nullptr); + if (error_info->error) + { + System::println( + System::Color::error, "Error: while loading %s: %s", error_info->name, error_info->error.message()); + } + } + + bool have_remaining_fields = false; + for (auto&& error_info : error_info_list) + { + if (!error_info->extra_fields.empty()) + { + System::println(System::Color::error, + "Error: There are invalid fields in the control file of %s", + error_info->name); + System::println("The following fields were not expected:\n\n %s\n", + Strings::join("\n ", error_info->extra_fields)); + have_remaining_fields = true; + } + } + + if (have_remaining_fields) + { + System::println("This is the list of valid fields (case-sensitive): \n\n %s\n", + Strings::join("\n ", get_list_of_valid_fields())); + System::println("Different source may be available for vcpkg. Use .\\bootstrap-vcpkg.bat to update.\n"); + } + + for (auto&& error_info : error_info_list) + { + if (!error_info->missing_fields.empty()) + { + System::println(System::Color::error, + "Error: There are missing fields in the control file of %s", + error_info->name); + System::println("The following fields were missing:\n\n %s\n", + Strings::join("\n ", error_info->missing_fields)); + } + } + } + + static ParseExpected<SourceParagraph> parse_source_paragraph(RawParagraph&& fields) + { + ParagraphParser parser(std::move(fields)); + + auto spgh = std::make_unique<SourceParagraph>(); + + parser.required_field(SourceParagraphFields::SOURCE, spgh->name); + parser.required_field(SourceParagraphFields::VERSION, spgh->version); + + spgh->description = parser.optional_field(SourceParagraphFields::DESCRIPTION); + spgh->maintainer = parser.optional_field(SourceParagraphFields::MAINTAINER); + spgh->depends = expand_qualified_dependencies( + parse_comma_list(parser.optional_field(SourceParagraphFields::BUILD_DEPENDS))); + spgh->supports = parse_comma_list(parser.optional_field(SourceParagraphFields::SUPPORTS)); + spgh->default_features = parse_comma_list(parser.optional_field(SourceParagraphFields::DEFAULTFEATURES)); + + auto err = parser.error_info(spgh->name); + if (err) + return std::move(err); + else + return std::move(spgh); + } + + static ParseExpected<FeatureParagraph> parse_feature_paragraph(RawParagraph&& fields) + { + ParagraphParser parser(std::move(fields)); + + auto fpgh = std::make_unique<FeatureParagraph>(); + + parser.required_field(SourceParagraphFields::FEATURE, fpgh->name); + parser.required_field(SourceParagraphFields::DESCRIPTION, fpgh->description); + + fpgh->depends = expand_qualified_dependencies( + parse_comma_list(parser.optional_field(SourceParagraphFields::BUILD_DEPENDS))); + + auto err = parser.error_info(fpgh->name); + if (err) + return std::move(err); + else + return std::move(fpgh); + } + + ParseExpected<SourceControlFile> SourceControlFile::parse_control_file( + std::vector<std::unordered_map<std::string, std::string>>&& control_paragraphs) + { + if (control_paragraphs.size() == 0) + { + return std::make_unique<Parse::ParseControlErrorInfo>(); + } + + auto control_file = std::make_unique<SourceControlFile>(); + + auto maybe_source = parse_source_paragraph(std::move(control_paragraphs.front())); + if (const auto source = maybe_source.get()) + control_file->core_paragraph = std::move(*source); + else + return std::move(maybe_source).error(); + + control_paragraphs.erase(control_paragraphs.begin()); + + for (auto&& feature_pgh : control_paragraphs) + { + auto maybe_feature = parse_feature_paragraph(std::move(feature_pgh)); + if (const auto feature = maybe_feature.get()) + control_file->feature_paragraphs.emplace_back(std::move(*feature)); + else + return std::move(maybe_feature).error(); + } + + return std::move(control_file); + } + + Optional<const FeatureParagraph&> SourceControlFile::find_feature(const std::string& featurename) const + { + auto it = Util::find_if(feature_paragraphs, + [&](const std::unique_ptr<FeatureParagraph>& p) { return p->name == featurename; }); + if (it != feature_paragraphs.end()) + return **it; + else + return nullopt; + } + + Dependency Dependency::parse_dependency(std::string name, std::string qualifier) + { + Dependency dep; + dep.qualifier = qualifier; + if (auto maybe_features = Features::from_string(name)) + dep.depend = *maybe_features.get(); + else + Checks::exit_with_message( + VCPKG_LINE_INFO, "error while parsing dependency: %s: %s", to_string(maybe_features.error()), name); + return dep; + } + + std::string Dependency::name() const + { + if (this->depend.features.empty()) return this->depend.name; + + const std::string features = Strings::join(",", this->depend.features); + return Strings::format("%s[%s]", this->depend.name, features); + } + + std::vector<Dependency> expand_qualified_dependencies(const std::vector<std::string>& depends) + { + return Util::fmap(depends, [&](const std::string& depend_string) -> Dependency { + auto pos = depend_string.find(' '); + if (pos == std::string::npos) return Dependency::parse_dependency(depend_string, ""); + // expect of the form "\w+ \[\w+\]" + Dependency dep; + + dep.depend.name = depend_string.substr(0, pos); + if (depend_string.c_str()[pos + 1] != '(' || depend_string[depend_string.size() - 1] != ')') + { + // Error, but for now just slurp the entire string. + return Dependency::parse_dependency(depend_string, ""); + } + dep.qualifier = depend_string.substr(pos + 2, depend_string.size() - pos - 3); + return dep; + }); + } + + std::vector<std::string> filter_dependencies(const std::vector<vcpkg::Dependency>& deps, const Triplet& t) + { + std::vector<std::string> ret; + for (auto&& dep : deps) + { + auto qualifiers = Strings::split(dep.qualifier, "&"); + if (std::all_of(qualifiers.begin(), qualifiers.end(), [&](const std::string& qualifier) { + if (qualifier.empty()) return true; + if (qualifier[0] == '!') + { + return t.canonical_name().find(qualifier.substr(1)) == std::string::npos; + } + return t.canonical_name().find(qualifier) != std::string::npos; + })) + { + ret.emplace_back(dep.name()); + } + } + return ret; + } + + std::vector<FeatureSpec> filter_dependencies_to_specs(const std::vector<Dependency>& deps, const Triplet& t) + { + return FeatureSpec::from_strings_and_triplet(filter_dependencies(deps, t), t); + } + + std::string to_string(const Dependency& dep) { return dep.name(); } + + ExpectedT<Supports, std::vector<std::string>> Supports::parse(const std::vector<std::string>& strs) + { + Supports ret; + std::vector<std::string> unrecognized; + + for (auto&& str : strs) + { + if (str == "x64") + ret.architectures.push_back(Architecture::X64); + else if (str == "x86") + ret.architectures.push_back(Architecture::X86); + else if (str == "arm") + ret.architectures.push_back(Architecture::ARM); + else if (str == "windows") + ret.platforms.push_back(Platform::WINDOWS); + else if (str == "uwp") + ret.platforms.push_back(Platform::UWP); + else if (str == "v140") + ret.toolsets.push_back(ToolsetVersion::V140); + else if (str == "v141") + ret.toolsets.push_back(ToolsetVersion::V141); + else if (str == "crt-static") + ret.crt_linkages.push_back(Linkage::STATIC); + else if (str == "crt-dynamic") + ret.crt_linkages.push_back(Linkage::DYNAMIC); + else + unrecognized.push_back(str); + } + + if (unrecognized.empty()) + return std::move(ret); + else + return std::move(unrecognized); + } + + bool Supports::is_supported(Architecture arch, Platform plat, Linkage crt, ToolsetVersion tools) + { + const auto is_in_or_empty = [](auto v, auto&& c) -> bool { return c.empty() || c.end() != Util::find(c, v); }; + if (!is_in_or_empty(arch, architectures)) return false; + if (!is_in_or_empty(plat, platforms)) return false; + if (!is_in_or_empty(crt, crt_linkages)) return false; + if (!is_in_or_empty(tools, toolsets)) return false; + return true; + } +} diff --git a/toolsrc/src/vcpkg/statusparagraph.cpp b/toolsrc/src/vcpkg/statusparagraph.cpp new file mode 100644 index 000000000..462d8d8ed --- /dev/null +++ b/toolsrc/src/vcpkg/statusparagraph.cpp @@ -0,0 +1,128 @@ +#include "pch.h" + +#include <vcpkg/base/util.h> +#include <vcpkg/statusparagraph.h> + +using namespace vcpkg::Parse; + +namespace vcpkg +{ + namespace BinaryParagraphRequiredField + { + static const std::string STATUS = "Status"; + } + + StatusParagraph::StatusParagraph() noexcept : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) {} + + void serialize(const StatusParagraph& pgh, std::string& out_str) + { + serialize(pgh.package, out_str); + out_str.append("Status: ") + .append(to_string(pgh.want)) + .append(" ok ") + .append(to_string(pgh.state)) + .push_back('\n'); + } + + StatusParagraph::StatusParagraph(std::unordered_map<std::string, std::string>&& fields) + : want(Want::ERROR_STATE), state(InstallState::ERROR_STATE) + { + auto status_it = fields.find(BinaryParagraphRequiredField::STATUS); + Checks::check_exit(VCPKG_LINE_INFO, status_it != fields.end(), "Expected 'Status' field in status paragraph"); + std::string status_field = std::move(status_it->second); + fields.erase(status_it); + + this->package = BinaryParagraph(std::move(fields)); + + auto b = status_field.begin(); + const auto mark = b; + const auto e = status_field.end(); + + // Todo: improve error handling + while (b != e && *b != ' ') + ++b; + + want = [](const std::string& text) { + if (text == "unknown") return Want::UNKNOWN; + if (text == "install") return Want::INSTALL; + if (text == "hold") return Want::HOLD; + if (text == "deinstall") return Want::DEINSTALL; + if (text == "purge") return Want::PURGE; + return Want::ERROR_STATE; + }(std::string(mark, b)); + + if (std::distance(b, e) < 4) return; + b += 4; + + state = [](const std::string& text) { + if (text == "not-installed") return InstallState::NOT_INSTALLED; + if (text == "installed") return InstallState::INSTALLED; + if (text == "half-installed") return InstallState::HALF_INSTALLED; + return InstallState::ERROR_STATE; + }(std::string(b, e)); + } + + std::string to_string(InstallState f) + { + switch (f) + { + case InstallState::HALF_INSTALLED: return "half-installed"; + case InstallState::INSTALLED: return "installed"; + case InstallState::NOT_INSTALLED: return "not-installed"; + default: return "error"; + } + } + + std::string to_string(Want f) + { + switch (f) + { + case Want::DEINSTALL: return "deinstall"; + case Want::HOLD: return "hold"; + case Want::INSTALL: return "install"; + case Want::PURGE: return "purge"; + case Want::UNKNOWN: return "unknown"; + default: return "error"; + } + } + std::vector<PackageSpec> InstalledPackageView::dependencies() const + { + // accumulate all features in installed dependencies + // Todo: make this unneeded by collapsing all package dependencies into the core package + auto deps = Util::fmap_flatten(features, [](const StatusParagraph* pgh) -> std::vector<std::string> const& { + return pgh->package.depends; + }); + + // Add the core paragraph dependencies to the list + deps.insert(deps.end(), core->package.depends.begin(), core->package.depends.end()); + + auto&& l_spec = spec(); + + // <hack> + // This is a hack to work around existing installations that put featurespecs into binary packages + // (example: curl[core]) + for (auto&& dep : deps) + { + dep.erase(std::find(dep.begin(), dep.end(), '['), dep.end()); + } + Util::unstable_keep_if(deps, [&](auto&& e) { return e != l_spec.name(); }); + // </hack> + Util::sort_unique_erase(deps); + + return Util::fmap(deps, [&](const std::string& dep) -> PackageSpec { + auto maybe_dependency_spec = PackageSpec::from_name_and_triplet(dep, l_spec.triplet()); + if (auto dependency_spec = maybe_dependency_spec.get()) + { + return std::move(*dependency_spec); + } + + const PackageSpecParseResult error_type = maybe_dependency_spec.error(); + Checks::exit_with_message(VCPKG_LINE_INFO, + "Invalid dependency [%s] in package [%s]\n" + "%s", + dep, + l_spec.name(), + vcpkg::to_string(error_type)); + }); + } +} diff --git a/toolsrc/src/vcpkg/statusparagraphs.cpp b/toolsrc/src/vcpkg/statusparagraphs.cpp new file mode 100644 index 000000000..c642af59b --- /dev/null +++ b/toolsrc/src/vcpkg/statusparagraphs.cpp @@ -0,0 +1,145 @@ +#include "pch.h" + +#include <vcpkg/base/checks.h> +#include <vcpkg/statusparagraphs.h> + +namespace vcpkg +{ + StatusParagraphs::StatusParagraphs() = default; + + StatusParagraphs::StatusParagraphs(std::vector<std::unique_ptr<StatusParagraph>>&& ps) + : paragraphs(std::move(ps)){}; + + std::vector<std::unique_ptr<StatusParagraph>*> StatusParagraphs::find_all(const std::string& name, + const Triplet& triplet) + { + std::vector<std::unique_ptr<StatusParagraph>*> spghs; + for (auto&& p : *this) + { + if (p->package.spec.name() == name && p->package.spec.triplet() == triplet) + { + if (p->package.feature.empty()) + spghs.emplace(spghs.begin(), &p); + else + spghs.emplace_back(&p); + } + } + return spghs; + } + + Optional<InstalledPackageView> StatusParagraphs::find_all_installed(const PackageSpec& spec) const + { + InstalledPackageView ipv; + for (auto&& p : *this) + { + if (p->package.spec.name() == spec.name() && p->package.spec.triplet() == spec.triplet() && + p->is_installed()) + { + if (p->package.feature.empty()) + { + Checks::check_exit(VCPKG_LINE_INFO, ipv.core == nullptr); + ipv.core = p.get(); + } + else + ipv.features.emplace_back(p.get()); + } + } + if (ipv.core != nullptr) + return std::move(ipv); + else + return nullopt; + } + + StatusParagraphs::iterator StatusParagraphs::find(const std::string& name, + const Triplet& triplet, + const std::string& feature) + { + if (feature == "core") + { + // The core feature maps to .feature == "" + return find(name, triplet, ""); + } + return std::find_if(begin(), end(), [&](const std::unique_ptr<StatusParagraph>& pgh) { + const PackageSpec& spec = pgh->package.spec; + return spec.name() == name && spec.triplet() == triplet && pgh->package.feature == feature; + }); + } + + StatusParagraphs::const_iterator StatusParagraphs::find(const std::string& name, + const Triplet& triplet, + const std::string& feature) const + { + if (feature == "core") + { + // The core feature maps to .feature == "" + return find(name, triplet, ""); + } + return std::find_if(begin(), end(), [&](const std::unique_ptr<StatusParagraph>& pgh) { + const PackageSpec& spec = pgh->package.spec; + return spec.name() == name && spec.triplet() == triplet && pgh->package.feature == feature; + }); + } + + StatusParagraphs::const_iterator StatusParagraphs::find_installed(const PackageSpec& spec) const + { + auto it = find(spec); + if (it != end() && (*it)->is_installed()) + { + return it; + } + else + { + return end(); + } + } + + StatusParagraphs::const_iterator StatusParagraphs::find_installed(const FeatureSpec& spec) const + { + auto it = find(spec); + if (it != end() && (*it)->is_installed()) + { + return it; + } + else + { + return end(); + } + } + + bool vcpkg::StatusParagraphs::is_installed(const PackageSpec& spec) const + { + auto it = find(spec); + return it != end() && (*it)->is_installed(); + } + + bool vcpkg::StatusParagraphs::is_installed(const FeatureSpec& spec) const + { + auto it = find(spec); + return it != end() && (*it)->is_installed(); + } + + StatusParagraphs::iterator StatusParagraphs::insert(std::unique_ptr<StatusParagraph> pgh) + { + Checks::check_exit(VCPKG_LINE_INFO, pgh != nullptr, "Inserted null paragraph"); + const PackageSpec& spec = pgh->package.spec; + const auto ptr = find(spec.name(), spec.triplet(), pgh->package.feature); + if (ptr == end()) + { + paragraphs.push_back(std::move(pgh)); + return paragraphs.rbegin(); + } + + // consume data from provided pgh. + **ptr = std::move(*pgh); + return ptr; + } + + void serialize(const StatusParagraphs& pghs, std::string& out_str) + { + for (auto& pgh : pghs.paragraphs) + { + serialize(*pgh, out_str); + out_str.push_back('\n'); + } + } +} diff --git a/toolsrc/src/vcpkg/triplet.cpp b/toolsrc/src/vcpkg/triplet.cpp new file mode 100644 index 000000000..c4ad3f690 --- /dev/null +++ b/toolsrc/src/vcpkg/triplet.cpp @@ -0,0 +1,57 @@ +#include "pch.h" + +#include <vcpkg/base/strings.h> +#include <vcpkg/triplet.h> + +namespace vcpkg +{ + struct TripletInstance + { + TripletInstance(std::string&& s) : value(std::move(s)), hash(std::hash<std::string>()(value)) {} + + const std::string value; + const size_t hash = 0; + + bool operator==(const TripletInstance& o) const { return o.value == value; } + }; + const TripletInstance Triplet::DEFAULT_INSTANCE({}); +} + +namespace std +{ + template<> + struct hash<vcpkg::TripletInstance> + { + size_t operator()(const vcpkg::TripletInstance& t) const { return t.hash; } + }; +} + +namespace vcpkg +{ + static std::unordered_set<TripletInstance> g_triplet_instances; + + const Triplet Triplet::X86_WINDOWS = from_canonical_name("x86-windows"); + const Triplet Triplet::X64_WINDOWS = from_canonical_name("x64-windows"); + const Triplet Triplet::X86_UWP = from_canonical_name("x86-uwp"); + const Triplet Triplet::X64_UWP = from_canonical_name("x64-uwp"); + const Triplet Triplet::ARM_UWP = from_canonical_name("arm-uwp"); + const Triplet Triplet::ARM64_UWP = from_canonical_name("arm64-uwp"); + const Triplet Triplet::ARM_WINDOWS = from_canonical_name("arm-windows"); + const Triplet Triplet::ARM64_WINDOWS = from_canonical_name("arm64-windows"); + + bool Triplet::operator==(const Triplet& other) const { return this->m_instance == other.m_instance; } + + bool operator!=(const Triplet& left, const Triplet& right) { return !(left == right); } + + Triplet Triplet::from_canonical_name(const std::string& triplet_as_string) + { + std::string s(Strings::ascii_to_lowercase(triplet_as_string)); + const auto p = g_triplet_instances.emplace(std::move(s)); + return &*p.first; + } + + const std::string& Triplet::canonical_name() const { return this->m_instance->value; } + + const std::string& Triplet::to_string() const { return this->canonical_name(); } + size_t Triplet::hash_code() const { return m_instance->hash; } +} diff --git a/toolsrc/src/vcpkg/update.cpp b/toolsrc/src/vcpkg/update.cpp new file mode 100644 index 000000000..57259f952 --- /dev/null +++ b/toolsrc/src/vcpkg/update.cpp @@ -0,0 +1,86 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/help.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/update.h> +#include <vcpkg/vcpkglib.h> + +namespace vcpkg::Update +{ + bool OutdatedPackage::compare_by_name(const OutdatedPackage& left, const OutdatedPackage& right) + { + return left.spec.name() < right.spec.name(); + } + + std::vector<OutdatedPackage> find_outdated_packages(const Dependencies::PortFileProvider& provider, + const StatusParagraphs& status_db) + { + auto installed_packages = get_installed_ports(status_db); + + std::vector<OutdatedPackage> output; + for (auto&& ipv : installed_packages) + { + const auto& pgh = ipv.core; + auto maybe_scf = provider.get_control_file(pgh->package.spec.name()); + if (auto p_scf = maybe_scf.get()) + { + auto&& port_version = p_scf->core_paragraph->version; + auto&& installed_version = pgh->package.version; + if (installed_version != port_version) + { + output.push_back({pgh->package.spec, VersionDiff(installed_version, port_version)}); + } + } + else + { + // No portfile available + } + } + + return output; + } + + const CommandStructure COMMAND_STRUCTURE = { + Help::create_example_string("update"), + 0, + 0, + {}, + nullptr, + }; + + void perform_and_exit(const VcpkgCmdArguments& args, const VcpkgPaths& paths) + { + args.parse_arguments(COMMAND_STRUCTURE); + System::println("Using local portfile versions. To update the local portfiles, use `git pull`."); + + const StatusParagraphs status_db = database_load_check(paths); + + Dependencies::PathsPortFileProvider provider(paths); + + const auto outdated_packages = SortedVector<OutdatedPackage>(find_outdated_packages(provider, status_db), + &OutdatedPackage::compare_by_name); + + if (outdated_packages.empty()) + { + System::println("No packages need updating."); + } + else + { + System::println("The following packages differ from their port versions:"); + for (auto&& package : outdated_packages) + { + System::println(" %-32s %s", package.spec, package.version_diff.to_string()); + } + System::println("\n" + "To update these packages and all dependencies, run\n" + " .\\vcpkg upgrade\n" + "\n" + "To only remove outdated packages, run\n" + " .\\vcpkg remove --outdated\n"); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} diff --git a/toolsrc/src/vcpkg/userconfig.cpp b/toolsrc/src/vcpkg/userconfig.cpp new file mode 100644 index 000000000..4945fdaaa --- /dev/null +++ b/toolsrc/src/vcpkg/userconfig.cpp @@ -0,0 +1,102 @@ +#include "pch.h" + +#include <vcpkg/base/files.h> +#include <vcpkg/base/lazy.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/userconfig.h> + +#if defined(_WIN32) +namespace +{ + static vcpkg::Lazy<fs::path> s_localappdata; + + static const fs::path& get_localappdata() + { + return s_localappdata.get_lazy([]() { + 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); + } + return localappdata; + }); + } +} +#endif + +namespace vcpkg +{ + fs::path get_user_dir() + { +#if defined(_WIN32) + return get_localappdata() / "vcpkg"; +#else + auto maybe_home = System::get_environment_variable("HOME"); + return fs::path(maybe_home.value_or("/var")) / ".vcpkg"; +#endif + } + + static fs::path get_config_path() + { + return get_user_dir() / "config"; + } + + UserConfig UserConfig::try_read_data(const Files::Filesystem& fs) + { + UserConfig ret; + try + { + auto maybe_pghs = Paragraphs::get_paragraphs(fs, get_config_path()); + if (const auto p_pghs = maybe_pghs.get()) + { + const auto& pghs = *p_pghs; + + std::unordered_map<std::string, std::string> keys; + if (pghs.size() > 0) keys = pghs[0]; + + for (size_t x = 1; x < pghs.size(); ++x) + { + for (auto&& p : pghs[x]) + keys.insert(p); + } + + ret.user_id = keys["User-Id"]; + ret.user_time = keys["User-Since"]; + ret.user_mac = keys["Mac-Hash"]; + ret.last_completed_survey = keys["Survey-Completed"]; + } + } + catch (...) + { + } + + return ret; + } + + void UserConfig::try_write_data(Files::Filesystem& fs) const + { + try + { + auto config_path = get_config_path(); + auto config_dir = config_path.parent_path(); + std::error_code ec; + fs.create_directory(config_dir, ec); + fs.write_contents(config_path, + Strings::format("User-Id: %s\n" + "User-Since: %s\n" + "Mac-Hash: %s\n" + "Survey-Completed: %s\n", + user_id, + user_time, + user_mac, + last_completed_survey), + ec); + } + catch (...) + { + } + } +} diff --git a/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp new file mode 100644 index 000000000..8909e1552 --- /dev/null +++ b/toolsrc/src/vcpkg/vcpkgcmdarguments.cpp @@ -0,0 +1,296 @@ +#include "pch.h" + +#include <vcpkg/base/system.h> +#include <vcpkg/commands.h> +#include <vcpkg/globalstate.h> +#include <vcpkg/help.h> +#include <vcpkg/metrics.h> +#include <vcpkg/vcpkgcmdarguments.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); + Metrics::g_metrics.lock()->track_property("error", "error option name"); + Help::print_usage(); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + if (option_field != nullptr) + { + System::println(System::Color::error, "Error: %s specified multiple times", option_name); + Metrics::g_metrics.lock()->track_property("error", "error option specified multiple times"); + Help::print_usage(); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + option_field = std::make_unique<std::string>(*arg_begin); + } + + static void parse_switch(bool new_setting, const std::string& option_name, Optional<bool>& option_field) + { + if (option_field && option_field != new_setting) + { + System::println(System::Color::error, "Error: conflicting values specified for --%s", option_name); + Metrics::g_metrics.lock()->track_property("error", "error conflicting switches"); + Help::print_usage(); + Checks::exit_fail(VCPKG_LINE_INFO); + } + option_field = new_setting; + } + + VcpkgCmdArguments VcpkgCmdArguments::create_from_command_line(const int argc, + const CommandLineCharType* const* const argv) + { + std::vector<std::string> v; + for (int i = 1; i < argc; ++i) + { +#if defined(_WIN32) + v.push_back(Strings::to_utf8(argv[i])); +#else + v.push_back(argv[i]); +#endif + } + + return VcpkgCmdArguments::create_from_arg_sequence(v.data(), v.data() + v.size()); + } + + VcpkgCmdArguments VcpkgCmdArguments::create_from_arg_sequence(const std::string* arg_begin, + const std::string* arg_end) + { + VcpkgCmdArguments args; + + for (; arg_begin != arg_end; ++arg_begin) + { + std::string arg = *arg_begin; + + if (arg.empty()) + { + continue; + } + + if (arg[0] == '-' && arg[1] != '-') + { + Metrics::g_metrics.lock()->track_property("error", "error short options are not supported"); + Checks::exit_with_message(VCPKG_LINE_INFO, "Error: short options are not supported: %s", arg); + } + + if (arg[0] == '-' && arg[1] == '-') + { + // make argument case insensitive + auto& f = std::use_facet<std::ctype<char>>(std::locale()); + f.tolower(&arg[0], &arg[0] + arg.size()); + // 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.triplet); + continue; + } + if (arg == "--debug") + { + parse_switch(true, "debug", args.debug); + continue; + } + if (arg == "--sendmetrics") + { + parse_switch(true, "sendmetrics", args.sendmetrics); + continue; + } + if (arg == "--printmetrics") + { + parse_switch(true, "printmetrics", args.printmetrics); + continue; + } + if (arg == "--no-sendmetrics") + { + parse_switch(false, "sendmetrics", args.sendmetrics); + continue; + } + if (arg == "--no-printmetrics") + { + parse_switch(false, "printmetrics", args.printmetrics); + continue; + } + if (arg == "--featurepackages") + { + parse_switch(true, "featurepackages", args.featurepackages); + continue; + } + if (arg == "--no-featurepackages") + { + parse_switch(false, "featurepackages", args.featurepackages); + continue; + } + if (arg == "--binarycaching") + { + parse_switch(true, "binarycaching", args.binarycaching); + continue; + } + if (arg == "--no-binarycaching") + { + parse_switch(false, "binarycaching", args.binarycaching); + continue; + } + + const auto eq_pos = arg.find('='); + if (eq_pos != std::string::npos) + { + args.optional_command_arguments.emplace(arg.substr(0, eq_pos), arg.substr(eq_pos + 1)); + } + else + { + args.optional_command_arguments.emplace(arg, nullopt); + } + continue; + } + + if (args.command.empty()) + { + args.command = arg; + } + else + { + args.command_arguments.push_back(arg); + } + } + + return args; + } + + ParsedArguments VcpkgCmdArguments::parse_arguments(const CommandStructure& command_structure) const + { + bool failed = false; + ParsedArguments output; + + const size_t actual_arg_count = command_arguments.size(); + + if (command_structure.minimum_arity == command_structure.maximum_arity) + { + if (actual_arg_count != command_structure.minimum_arity) + { + System::println(System::Color::error, + "Error: '%s' requires %u arguments, but %u were provided.", + this->command, + command_structure.minimum_arity, + actual_arg_count); + failed = true; + } + } + else + { + if (actual_arg_count < command_structure.minimum_arity) + { + System::println(System::Color::error, + "Error: '%s' requires at least %u arguments, but %u were provided", + this->command, + command_structure.minimum_arity, + actual_arg_count); + failed = true; + } + if (actual_arg_count > command_structure.maximum_arity) + { + System::println(System::Color::error, + "Error: '%s' requires at most %u arguments, but %u were provided", + this->command, + command_structure.maximum_arity, + actual_arg_count); + failed = true; + } + } + + auto options_copy = this->optional_command_arguments; + for (auto&& option : command_structure.options.switches) + { + const auto it = options_copy.find(option.name); + if (it != options_copy.end()) + { + if (it->second.has_value()) + { + // Having a string value indicates it was passed like '--a=xyz' + System::println( + System::Color::error, "Error: The option '%s' does not accept an argument.", option.name); + failed = true; + } + else + { + output.switches.insert(option.name); + options_copy.erase(it); + } + } + } + + for (auto&& option : command_structure.options.settings) + { + const auto it = options_copy.find(option.name); + if (it != options_copy.end()) + { + if (!it->second.has_value()) + { + // Not having a string value indicates it was passed like '--a' + System::println( + System::Color::error, "Error: The option '%s' must be passed an argument.", option.name); + failed = true; + } + else + { + output.settings.emplace(option.name, it->second.value_or_exit(VCPKG_LINE_INFO)); + options_copy.erase(it); + } + } + } + + if (!options_copy.empty()) + { + System::println(System::Color::error, "Unknown option(s) for command '%s':", this->command); + for (auto&& option : options_copy) + { + System::println(" %s", option.first); + } + System::println(); + failed = true; + } + + if (failed) + { + display_usage(command_structure); + Checks::exit_fail(VCPKG_LINE_INFO); + } + + return output; + } + + void display_usage(const CommandStructure& command_structure) + { + if (!command_structure.example_text.empty()) + { + System::println("%s", command_structure.example_text); + } + + System::println("Options:"); + for (auto&& option : command_structure.options.switches) + { + System::println(" %-40s %s", option.name, option.short_help_text); + } + for (auto&& option : command_structure.options.settings) + { + System::println(" %-40s %s", (option.name + "=..."), option.short_help_text); + } + System::println(" %-40s %s", "--triplet <t>", "Set the default triplet for unqualified packages"); + System::println(" %-40s %s", + "--vcpkg-root <path>", + "Specify the vcpkg directory to use instead of current directory or tool directory"); + } +} diff --git a/toolsrc/src/vcpkglib.cpp b/toolsrc/src/vcpkg/vcpkglib.cpp index 15130f77e..c8e95dab1 100644 --- a/toolsrc/src/vcpkglib.cpp +++ b/toolsrc/src/vcpkg/vcpkglib.cpp @@ -1,16 +1,14 @@ #include "pch.h" -#include "Paragraphs.h" -#include "metrics.h" -#include "vcpkg_Files.h" -#include "vcpkg_Strings.h" -#include "vcpkg_Util.h" -#include "vcpkglib.h" +#include <vcpkg/base/files.h> +#include <vcpkg/base/strings.h> +#include <vcpkg/base/util.h> +#include <vcpkg/metrics.h> +#include <vcpkg/paragraphs.h> +#include <vcpkg/vcpkglib.h> namespace vcpkg { - bool g_debugging = false; - static StatusParagraphs load_current_database(Files::Filesystem& fs, const fs::path& vcpkg_dir_status_file, const fs::path& vcpkg_dir_status_file_old) @@ -31,7 +29,7 @@ namespace vcpkg std::vector<std::unique_ptr<StatusParagraph>> status_pghs; for (auto&& p : pghs) { - status_pghs.push_back(std::make_unique<StatusParagraph>(p)); + status_pghs.push_back(std::make_unique<StatusParagraph>(std::move(p))); } return StatusParagraphs(std::move(status_pghs)); @@ -41,7 +39,7 @@ namespace vcpkg { auto& fs = paths.get_filesystem(); - auto updates_dir = paths.vcpkg_dir_updates; + const auto updates_dir = paths.vcpkg_dir_updates; std::error_code ec; fs.create_directory(paths.installed, ec); @@ -56,6 +54,7 @@ namespace vcpkg StatusParagraphs current_status_db = load_current_database(fs, status_file, status_file_old); auto update_files = fs.get_files_non_recursive(updates_dir); + Util::sort(update_files); if (update_files.empty()) { // updates directory is empty, control file is up-to-date. @@ -69,7 +68,7 @@ namespace vcpkg auto pghs = Paragraphs::get_paragraphs(fs, file).value_or_exit(VCPKG_LINE_INFO); for (auto&& p : pghs) { - current_status_db.insert(std::make_unique<StatusParagraph>(p)); + current_status_db.insert(std::make_unique<StatusParagraph>(std::move(p))); } } @@ -92,9 +91,9 @@ namespace vcpkg static int update_id = 0; auto& fs = paths.get_filesystem(); - 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); + const auto my_update_id = update_id++; + const auto tmp_update_filename = paths.vcpkg_dir_updates / "incomplete"; + const auto update_filename = paths.vcpkg_dir_updates / Strings::format("%010d", my_update_id); fs.write_contents(tmp_update_filename, Strings::serialize(p)); fs.rename(tmp_update_filename, update_filename); @@ -119,7 +118,7 @@ namespace vcpkg if (!was_tracked) { was_tracked = true; - Metrics::track_property("listfile", "update to new format"); + Metrics::g_metrics.lock()->track_property("listfile", "update to new format"); } // The files are sorted such that directories are placed just before the files they contain @@ -170,16 +169,32 @@ namespace vcpkg fs.rename(updated_listfile_path, listfile_path); } - std::vector<StatusParagraph*> get_installed_ports(const StatusParagraphs& status_db) + std::vector<InstalledPackageView> get_installed_ports(const StatusParagraphs& status_db) { - std::vector<StatusParagraph*> installed_packages; + std::map<PackageSpec, InstalledPackageView> ipv_map; + + std::vector<InstalledPackageView> installed_packages; for (auto&& pgh : status_db) { - if (pgh->state != InstallState::INSTALLED || pgh->want != Want::INSTALL) continue; - installed_packages.push_back(pgh.get()); + if (!pgh->is_installed()) continue; + auto& ipv = ipv_map[pgh->package.spec]; + if (pgh->package.feature.empty()) + { + ipv.core = pgh.get(); + } + else + { + ipv.features.emplace_back(pgh.get()); + } } - return installed_packages; + for (auto&& ipv : ipv_map) + Checks::check_exit(VCPKG_LINE_INFO, + ipv.second.core != nullptr, + "Database is corrupted: package %s has features but no core paragraph.", + ipv.first); + + return Util::fmap(ipv_map, [](auto&& p) -> InstalledPackageView { return std::move(p.second); }); } std::vector<StatusParagraphAndAssociatedFiles> get_installed_files(const VcpkgPaths& paths, @@ -191,7 +206,7 @@ namespace vcpkg for (const std::unique_ptr<StatusParagraph>& pgh : status_db) { - if (pgh->state != InstallState::INSTALLED) + if (!pgh->is_installed() || !pgh->package.feature.empty()) { continue; } @@ -214,29 +229,10 @@ namespace vcpkg return installed_files; } - CMakeVariable::CMakeVariable(const CWStringView varname, const wchar_t* varvalue) - : s(Strings::wformat(LR"("-D%s=%s")", varname, varvalue)) - { - } - CMakeVariable::CMakeVariable(const CWStringView varname, const std::string& varvalue) - : CMakeVariable(varname, Strings::to_utf16(varvalue).c_str()) - { - } - CMakeVariable::CMakeVariable(const CWStringView varname, const std::wstring& varvalue) - : CMakeVariable(varname, varvalue.c_str()) - { - } - CMakeVariable::CMakeVariable(const CWStringView varname, const fs::path& path) - : CMakeVariable(varname, path.generic_wstring()) - { - } - - std::wstring make_cmake_cmd(const fs::path& cmake_exe, - const fs::path& cmake_script, - const std::vector<CMakeVariable>& pass_variables) + std::string shorten_text(const std::string& desc, const size_t length) { - std::wstring cmd_cmake_pass_variables = Strings::join(L" ", pass_variables, [](auto&& v) { return v.s; }); - return Strings::wformat( - LR"("%s" %s -P "%s")", cmake_exe.native(), cmd_cmake_pass_variables, cmake_script.generic_wstring()); + Checks::check_exit(VCPKG_LINE_INFO, length >= 3); + auto simple_desc = std::regex_replace(desc, std::regex("\\s+"), " "); + return simple_desc.size() <= length ? simple_desc : simple_desc.substr(0, length - 3) + "..."; } } diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp new file mode 100644 index 000000000..9b74bea74 --- /dev/null +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -0,0 +1,170 @@ +#include "pch.h" + +#include <vcpkg/base/expected.h> +#include <vcpkg/base/files.h> +#include <vcpkg/base/system.h> +#include <vcpkg/base/util.h> +#include <vcpkg/build.h> +#include <vcpkg/commands.h> +#include <vcpkg/metrics.h> +#include <vcpkg/packagespec.h> +#include <vcpkg/vcpkgpaths.h> + +namespace vcpkg +{ + Expected<VcpkgPaths> VcpkgPaths::create(const fs::path& vcpkg_root_dir, const std::string& default_vs_path) + { + std::error_code ec; + const fs::path canonical_vcpkg_root_dir = fs::stdfs::canonical(vcpkg_root_dir, ec); + if (ec) + { + return ec; + } + + VcpkgPaths paths; + paths.root = canonical_vcpkg_root_dir; + paths.default_vs_path = default_vs_path; + + if (paths.root.empty()) + { + Metrics::g_metrics.lock()->track_property("error", "Invalid vcpkg root directory"); + Checks::exit_with_message(VCPKG_LINE_INFO, "Invalid vcpkg root directory: %s", paths.root.string()); + } + + 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.scripts = paths.root / "scripts"; + + paths.tools = paths.downloads / "tools"; + paths.buildsystems = paths.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.scripts / "ports.cmake"; + + return paths; + } + + fs::path VcpkgPaths::package_dir(const PackageSpec& spec) const { return this->packages / spec.dir(); } + + fs::path VcpkgPaths::port_dir(const PackageSpec& spec) const { return this->ports / spec.name(); } + fs::path VcpkgPaths::port_dir(const std::string& name) const { return this->ports / name; } + + fs::path VcpkgPaths::build_info_file_path(const PackageSpec& spec) const + { + return this->package_dir(spec) / "BUILD_INFO"; + } + + fs::path VcpkgPaths::listfile_path(const BinaryParagraph& pgh) const + { + return this->vcpkg_dir_info / (pgh.fullstem() + ".list"); + } + + const std::vector<std::string>& VcpkgPaths::get_available_triplets() const + { + return this->available_triplets.get_lazy([this]() -> std::vector<std::string> { + std::vector<std::string> output; + for (auto&& path : this->get_filesystem().get_files_non_recursive(this->triplets)) + { + output.push_back(path.stem().filename().string()); + } + Util::sort(output); + + return output; + }); + } + + bool VcpkgPaths::is_valid_triplet(const Triplet& t) const + { + const auto it = Util::find_if(this->get_available_triplets(), [&](auto&& available_triplet) { + return t.canonical_name() == available_triplet; + }); + return it != this->get_available_triplets().cend(); + } + + const fs::path& VcpkgPaths::get_tool_exe(const std::string& tool) const + { + return this->tool_paths.get_lazy(tool, [&]() { return Commands::Fetch::get_tool_path(*this, tool); }); + } + + const Toolset& VcpkgPaths::get_toolset(const Build::PreBuildInfo& prebuildinfo) const + { + if (prebuildinfo.external_toolchain_file || + (!prebuildinfo.cmake_system_name.empty() && prebuildinfo.cmake_system_name != "WindowsStore")) + { + static Toolset external_toolset = []() -> Toolset { + Toolset ret; + ret.dumpbin = ""; + ret.supported_architectures = { + ToolsetArchOption{"", System::get_host_processor(), System::get_host_processor()}}; + ret.vcvarsall = ""; + ret.vcvarsall_options = {}; + ret.version = "external"; + ret.visual_studio_root_path = ""; + return ret; + }(); + return external_toolset; + } + +#if !defined(_WIN32) + Checks::exit_with_message(VCPKG_LINE_INFO, "Cannot build windows triplets from non-windows."); +#else + const std::vector<Toolset>& vs_toolsets = this->toolsets.get_lazy( + [this]() { return Commands::Fetch::find_toolset_instances_preferred_first(*this); }); + + std::vector<const Toolset*> candidates = Util::element_pointers(vs_toolsets); + const auto tsv = prebuildinfo.platform_toolset.get(); + auto vsp = prebuildinfo.visual_studio_path.get(); + if (!vsp && !default_vs_path.empty()) + { + vsp = &default_vs_path; + } + + if (tsv && vsp) + { + Util::stable_keep_if( + candidates, [&](const Toolset* t) { return *tsv == t->version && *vsp == t->visual_studio_root_path; }); + Checks::check_exit(VCPKG_LINE_INFO, + !candidates.empty(), + "Could not find Visual Studio instance at %s with %s toolset.", + vsp->u8string(), + *tsv); + + Checks::check_exit(VCPKG_LINE_INFO, candidates.size() == 1); + return *candidates.back(); + } + + if (tsv) + { + Util::stable_keep_if(candidates, [&](const Toolset* t) { return *tsv == t->version; }); + Checks::check_exit( + VCPKG_LINE_INFO, !candidates.empty(), "Could not find Visual Studio instance with %s toolset.", *tsv); + } + + if (vsp) + { + const fs::path vs_root_path = *vsp; + Util::stable_keep_if(candidates, + [&](const Toolset* t) { return vs_root_path == t->visual_studio_root_path; }); + Checks::check_exit(VCPKG_LINE_INFO, + !candidates.empty(), + "Could not find Visual Studio instance at %s.", + vs_root_path.generic_string()); + } + + Checks::check_exit(VCPKG_LINE_INFO, !candidates.empty(), "No suitable Visual Studio instances were found"); + return *candidates.front(); + +#endif + } + + Files::Filesystem& VcpkgPaths::get_filesystem() const { return Files::get_real_filesystem(); } +} diff --git a/toolsrc/src/vcpkg/versiont.cpp b/toolsrc/src/vcpkg/versiont.cpp new file mode 100644 index 000000000..d20e6b577 --- /dev/null +++ b/toolsrc/src/vcpkg/versiont.cpp @@ -0,0 +1,23 @@ +#include "pch.h" + +#include <vcpkg/base/strings.h> +#include <vcpkg/versiont.h> + +namespace vcpkg +{ + VersionT::VersionT() noexcept : value("0.0.0") {} + VersionT::VersionT(std::string&& value) : value(std::move(value)) {} + VersionT::VersionT(const std::string& value) : value(value) {} + const std::string& VersionT::to_string() const { return value; } + bool operator==(const VersionT& left, const VersionT& right) { return left.to_string() == right.to_string(); } + bool operator!=(const VersionT& left, const VersionT& right) { return left.to_string() != right.to_string(); } + std::string to_printf_arg(const VersionT& version) { return version.to_string(); } + + VersionDiff::VersionDiff() noexcept : left(), right() {} + VersionDiff::VersionDiff(const VersionT& left, const VersionT& right) : left(left), right(right) {} + + std::string VersionDiff::to_string() const + { + return Strings::format("%s -> %s", left.to_string(), right.to_string()); + } +} diff --git a/toolsrc/src/vcpkg_Build.cpp b/toolsrc/src/vcpkg_Build.cpp deleted file mode 100644 index 6605fa4fb..000000000 --- a/toolsrc/src/vcpkg_Build.cpp +++ /dev/null @@ -1,337 +0,0 @@ -#include "pch.h" - -#include "Paragraphs.h" -#include "PostBuildLint.h" -#include "metrics.h" -#include "vcpkg_Build.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Chrono.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Enums.h" -#include "vcpkg_System.h" -#include "vcpkg_optional.h" -#include "vcpkglib.h" -#include "vcpkglib_helpers.h" - -using vcpkg::PostBuildLint::BuildPolicies; -namespace BuildPoliciesC = vcpkg::PostBuildLint::BuildPoliciesC; -using vcpkg::PostBuildLint::LinkageType; -namespace LinkageTypeC = vcpkg::PostBuildLint::LinkageTypeC; - -namespace vcpkg::Build -{ - namespace BuildInfoRequiredField - { - static const std::string CRT_LINKAGE = "CRTLinkage"; - static const std::string LIBRARY_LINKAGE = "LibraryLinkage"; - } - - CWStringView to_vcvarsall_target(const std::string& cmake_system_name) - { - if (cmake_system_name == "") return L""; - if (cmake_system_name == "Windows") return L""; - if (cmake_system_name == "WindowsStore") return L"store"; - - Checks::exit_with_message(VCPKG_LINE_INFO, "Unsupported vcvarsall target %s", cmake_system_name); - } - - CWStringView to_vcvarsall_toolchain(const std::string& target_architecture) - { - using CPU = System::CPUArchitecture; - - struct ArchOption - { - CWStringView name; - CPU host_arch; - CPU target_arch; - }; - - static constexpr ArchOption X86 = {L"x86", CPU::X86, CPU::X86}; - static constexpr ArchOption X86_X64 = {L"x86_x64", CPU::X86, CPU::X64}; - static constexpr ArchOption X86_ARM = {L"x86_arm", CPU::X86, CPU::ARM}; - static constexpr ArchOption X86_ARM64 = {L"x86_arm64", CPU::X86, CPU::ARM64}; - - static constexpr ArchOption X64 = {L"amd64", CPU::X64, CPU::X64}; - static constexpr ArchOption X64_X86 = {L"amd64_x86", CPU::X64, CPU::X86}; - static constexpr ArchOption X64_ARM = {L"amd64_arm", CPU::X64, CPU::ARM}; - static constexpr ArchOption X64_ARM64 = {L"amd64_arm64", CPU::X64, CPU::ARM64}; - - static constexpr std::array<ArchOption, 8> VALUES = { - X86, X86_X64, X86_ARM, X86_ARM64, X64, X64_X86, X64_ARM, X64_ARM64}; - - auto target_arch = System::to_cpu_architecture(target_architecture); - auto host_arch = System::get_host_processor(); - - for (auto&& value : VALUES) - { - if (target_arch == value.target_arch && host_arch == value.host_arch) - { - return value.name; - } - } - - Checks::exit_with_message(VCPKG_LINE_INFO, "Unsupported toolchain combination %s", target_architecture); - } - - std::wstring make_build_env_cmd(const PreBuildInfo& pre_build_info, const Toolset& toolset) - { - const wchar_t* tonull = L" >nul"; - if (g_debugging) - { - tonull = L""; - } - - auto arch = to_vcvarsall_toolchain(pre_build_info.target_architecture); - auto target = to_vcvarsall_target(pre_build_info.cmake_system_name); - - return Strings::wformat(LR"("%s" %s %s %s 2>&1)", toolset.vcvarsall.native(), arch, target, tonull); - } - - static void create_binary_control_file(const VcpkgPaths& paths, - const SourceParagraph& source_paragraph, - const Triplet& triplet, - const BuildInfo& build_info) - { - BinaryParagraph bpgh = BinaryParagraph(source_paragraph, triplet); - if (auto p_ver = build_info.version.get()) - { - bpgh.version = *p_ver; - } - const fs::path binary_control_file = paths.packages / bpgh.dir() / "CONTROL"; - paths.get_filesystem().write_contents(binary_control_file, Strings::serialize(bpgh)); - } - - ExtendedBuildResult build_package(const VcpkgPaths& paths, - const BuildPackageConfig& config, - const StatusParagraphs& status_db) - { - const PackageSpec spec = - PackageSpec::from_name_and_triplet(config.src.name, config.triplet).value_or_exit(VCPKG_LINE_INFO); - - const Triplet& triplet = config.triplet; - { - std::vector<PackageSpec> missing_specs; - for (auto&& dep : filter_dependencies(config.src.depends, triplet)) - { - if (status_db.find_installed(dep, triplet) == status_db.end()) - { - missing_specs.push_back( - PackageSpec::from_name_and_triplet(dep, triplet).value_or_exit(VCPKG_LINE_INFO)); - } - } - // Fail the build if any dependencies were missing - if (!missing_specs.empty()) - { - return {BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES, std::move(missing_specs)}; - } - } - - const fs::path& cmake_exe_path = paths.get_cmake_exe(); - const fs::path& git_exe_path = paths.get_git_exe(); - - const fs::path ports_cmake_script_path = paths.ports_cmake; - const Toolset& toolset = paths.get_toolset(); - auto pre_build_info = PreBuildInfo::from_triplet_file(paths, triplet); - const auto cmd_set_environment = make_build_env_cmd(pre_build_info, toolset); - - const std::wstring cmd_launch_cmake = - make_cmake_cmd(cmake_exe_path, - ports_cmake_script_path, - {{L"CMD", L"BUILD"}, - {L"PORT", config.src.name}, - {L"CURRENT_PORT_DIR", config.port_dir / "/."}, - {L"TARGET_TRIPLET", triplet.canonical_name()}, - {L"VCPKG_PLATFORM_TOOLSET", toolset.version}, - {L"VCPKG_USE_HEAD_VERSION", config.use_head_version ? L"1" : L"0"}, - {L"_VCPKG_NO_DOWNLOADS", config.no_downloads ? L"1" : L"0"}, - {L"GIT", git_exe_path}}); - - const std::wstring command = Strings::wformat(LR"(%s && %s)", cmd_set_environment, cmd_launch_cmake); - - const ElapsedTime timer = ElapsedTime::create_started(); - - int return_code = System::cmd_execute_clean(command); - auto buildtimeus = timer.microseconds(); - const auto spec_string = spec.to_string(); - Metrics::track_metric("buildtimeus-" + spec_string, buildtimeus); - - if (return_code != 0) - { - Metrics::track_property("error", "build failed"); - Metrics::track_property("build_error", spec_string); - return {BuildResult::BUILD_FAILED, {}}; - } - - auto build_info = read_build_info(paths.get_filesystem(), paths.build_info_file_path(spec)); - const size_t error_count = PostBuildLint::perform_all_checks(spec, paths, pre_build_info, build_info); - - if (error_count != 0) - { - return {BuildResult::POST_BUILD_CHECKS_FAILED, {}}; - } - - create_binary_control_file(paths, config.src, triplet, build_info); - - // const fs::path port_buildtrees_dir = paths.buildtrees / spec.name; - // delete_directory(port_buildtrees_dir); - - return {BuildResult::SUCCEEDED, {}}; - } - - const std::string& to_string(const BuildResult build_result) - { - static const std::string NULLVALUE_STRING = Enums::nullvalue_to_string("vcpkg::Commands::Build::BuildResult"); - static const std::string SUCCEEDED_STRING = "SUCCEEDED"; - static const std::string BUILD_FAILED_STRING = "BUILD_FAILED"; - static const std::string POST_BUILD_CHECKS_FAILED_STRING = "POST_BUILD_CHECKS_FAILED"; - static const std::string CASCADED_DUE_TO_MISSING_DEPENDENCIES_STRING = "CASCADED_DUE_TO_MISSING_DEPENDENCIES"; - - switch (build_result) - { - case BuildResult::NULLVALUE: return NULLVALUE_STRING; - case BuildResult::SUCCEEDED: return SUCCEEDED_STRING; - case BuildResult::BUILD_FAILED: return BUILD_FAILED_STRING; - case BuildResult::POST_BUILD_CHECKS_FAILED: return POST_BUILD_CHECKS_FAILED_STRING; - case BuildResult::CASCADED_DUE_TO_MISSING_DEPENDENCIES: return CASCADED_DUE_TO_MISSING_DEPENDENCIES_STRING; - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - - std::string create_error_message(const BuildResult build_result, const PackageSpec& spec) - { - return Strings::format("Error: Building package %s failed with: %s", spec, Build::to_string(build_result)); - } - - std::string create_user_troubleshooting_message(const PackageSpec& spec) - { - return Strings::format("Please ensure you're using the latest portfiles with `.\\vcpkg update`, then\n" - "submit an issue at https://github.com/Microsoft/vcpkg/issues including:\n" - " Package: %s\n" - " Vcpkg version: %s\n" - "\n" - "Additionally, attach any relevant sections from the log files above.", - spec, - Commands::Version::version()); - } - - BuildInfo BuildInfo::create(std::unordered_map<std::string, std::string> pgh) - { - BuildInfo build_info; - const std::string crt_linkage_as_string = - details::remove_required_field(&pgh, BuildInfoRequiredField::CRT_LINKAGE); - build_info.crt_linkage = LinkageType::value_of(crt_linkage_as_string); - Checks::check_exit(VCPKG_LINE_INFO, - build_info.crt_linkage != LinkageTypeC::NULLVALUE, - "Invalid crt linkage type: [%s]", - crt_linkage_as_string); - - const std::string library_linkage_as_string = - details::remove_required_field(&pgh, BuildInfoRequiredField::LIBRARY_LINKAGE); - build_info.library_linkage = LinkageType::value_of(library_linkage_as_string); - Checks::check_exit(VCPKG_LINE_INFO, - build_info.library_linkage != LinkageTypeC::NULLVALUE, - "Invalid library linkage type: [%s]", - library_linkage_as_string); - - auto it_version = pgh.find("Version"); - if (it_version != pgh.end()) - { - build_info.version = it_version->second; - pgh.erase(it_version); - } - - // The remaining entries are policies - for (const std::unordered_map<std::string, std::string>::value_type& p : pgh) - { - const BuildPolicies policy = BuildPolicies::parse(p.first); - Checks::check_exit( - VCPKG_LINE_INFO, policy != BuildPoliciesC::NULLVALUE, "Unknown policy found: %s", p.first); - if (p.second == "enabled") - build_info.policies.emplace(policy, true); - else if (p.second == "disabled") - build_info.policies.emplace(policy, false); - else - Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown setting for policy '%s': %s", p.first, p.second); - } - - return build_info; - } - - BuildInfo read_build_info(const Files::Filesystem& fs, const fs::path& filepath) - { - const Expected<std::unordered_map<std::string, std::string>> pghs = - Paragraphs::get_single_paragraph(fs, filepath); - Checks::check_exit(VCPKG_LINE_INFO, pghs.get() != nullptr, "Invalid BUILD_INFO file for package"); - return BuildInfo::create(*pghs.get()); - } - - PreBuildInfo PreBuildInfo::from_triplet_file(const VcpkgPaths& paths, const Triplet& triplet) - { - static constexpr CStringView FLAG_GUID = "c35112b6-d1ba-415b-aa5d-81de856ef8eb"; - - const fs::path& cmake_exe_path = paths.get_cmake_exe(); - const fs::path ports_cmake_script_path = paths.scripts / "get_triplet_environment.cmake"; - const fs::path triplet_file_path = paths.triplets / (triplet.canonical_name() + ".cmake"); - - const std::wstring cmd_launch_cmake = make_cmake_cmd(cmake_exe_path, - ports_cmake_script_path, - { - {L"CMAKE_TRIPLET_FILE", triplet_file_path}, - }); - - const std::wstring command = Strings::wformat(LR"(%s)", cmd_launch_cmake); - auto ec_data = System::cmd_execute_and_capture_output(command); - Checks::check_exit(VCPKG_LINE_INFO, ec_data.exit_code == 0); - - const std::vector<std::string> lines = Strings::split(ec_data.output, "\n"); - - PreBuildInfo pre_build_info; - - auto e = lines.cend(); - auto cur = std::find(lines.cbegin(), e, FLAG_GUID); - if (cur != e) ++cur; - - for (; cur != e; ++cur) - { - auto&& line = *cur; - - const std::vector<std::string> s = Strings::split(line, "="); - Checks::check_exit(VCPKG_LINE_INFO, - s.size() == 1 || s.size() == 2, - "Expected format is [VARIABLE_NAME=VARIABLE_VALUE], but was [%s]", - line); - - const bool variable_with_no_value = s.size() == 1; - const std::string variable_name = s.at(0); - const std::string variable_value = variable_with_no_value ? "" : s.at(1); - - if (variable_name == "VCPKG_TARGET_ARCHITECTURE") - { - pre_build_info.target_architecture = variable_value; - continue; - } - - if (variable_name == "VCPKG_CMAKE_SYSTEM_NAME") - { - pre_build_info.cmake_system_name = variable_value; - continue; - } - - if (variable_name == "VCPKG_CMAKE_SYSTEM_VERSION") - { - pre_build_info.cmake_system_version = variable_value; - continue; - } - - if (variable_name == "VCPKG_PLATFORM_TOOLSET") - { - pre_build_info.platform_toolset = variable_value; - continue; - } - - Checks::exit_with_message(VCPKG_LINE_INFO, "Unknown variable name %s", line); - } - - return pre_build_info; - } -} diff --git a/toolsrc/src/vcpkg_Checks.cpp b/toolsrc/src/vcpkg_Checks.cpp deleted file mode 100644 index 2674b889a..000000000 --- a/toolsrc/src/vcpkg_Checks.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Checks.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" - -namespace vcpkg::Checks -{ - [[noreturn]] void unreachable(const LineInfo& line_info) - { - System::println(System::Color::error, "Error: Unreachable code was reached"); - System::println(System::Color::error, line_info.to_string()); // Always print line_info here -#ifndef NDEBUG - std::abort(); -#else - ::exit(EXIT_FAILURE); -#endif - } - - [[noreturn]] void exit_with_code(const LineInfo& line_info, const int exit_code) - { - Debug::println(System::Color::error, line_info.to_string()); - ::exit(exit_code); - } - - [[noreturn]] void exit_with_message(const LineInfo& line_info, const CStringView errorMessage) - { - System::println(System::Color::error, errorMessage); - exit_fail(line_info); - } - - void check_exit(const LineInfo& line_info, bool expression) - { - if (!expression) - { - exit_with_message(line_info, ""); - } - } - - void check_exit(const LineInfo& line_info, bool expression, const CStringView errorMessage) - { - if (!expression) - { - exit_with_message(line_info, errorMessage); - } - } -} diff --git a/toolsrc/src/vcpkg_Chrono.cpp b/toolsrc/src/vcpkg_Chrono.cpp deleted file mode 100644 index d70f4a054..000000000 --- a/toolsrc/src/vcpkg_Chrono.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Checks.h" -#include "vcpkg_Chrono.h" - -namespace vcpkg -{ - static std::string format_time_userfriendly(const std::chrono::nanoseconds& nanos) - { - using std::chrono::hours; - using std::chrono::minutes; - using std::chrono::seconds; - using std::chrono::milliseconds; - using std::chrono::microseconds; - using std::chrono::nanoseconds; - using std::chrono::duration_cast; - - const double nanos_as_double = static_cast<double>(nanos.count()); - - if (duration_cast<hours>(nanos) > hours()) - { - auto t = nanos_as_double / duration_cast<nanoseconds>(hours(1)).count(); - return Strings::format("%.4g h", t); - } - - if (duration_cast<minutes>(nanos) > minutes()) - { - auto t = nanos_as_double / duration_cast<nanoseconds>(minutes(1)).count(); - return Strings::format("%.4g min", t); - } - - if (duration_cast<seconds>(nanos) > seconds()) - { - auto t = nanos_as_double / duration_cast<nanoseconds>(seconds(1)).count(); - return Strings::format("%.4g s", t); - } - - if (duration_cast<milliseconds>(nanos) > milliseconds()) - { - auto t = nanos_as_double / duration_cast<nanoseconds>(milliseconds(1)).count(); - return Strings::format("%.4g ms", t); - } - - if (duration_cast<microseconds>(nanos) > microseconds()) - { - auto t = nanos_as_double / duration_cast<nanoseconds>(microseconds(1)).count(); - return Strings::format("%.4g us", t); - } - - return Strings::format("%.4g ns", nanos_as_double); - } - - ElapsedTime ElapsedTime::create_started() - { - ElapsedTime t; - t.m_start_tick = std::chrono::high_resolution_clock::now(); - return t; - } - - std::string ElapsedTime::to_string() const { return format_time_userfriendly(elapsed<std::chrono::nanoseconds>()); } -} diff --git a/toolsrc/src/vcpkg_Dependencies.cpp b/toolsrc/src/vcpkg_Dependencies.cpp deleted file mode 100644 index acfb55239..000000000 --- a/toolsrc/src/vcpkg_Dependencies.cpp +++ /dev/null @@ -1,300 +0,0 @@ -#include "pch.h" - -#include "PackageSpec.h" -#include "Paragraphs.h" -#include "StatusParagraphs.h" -#include "VcpkgPaths.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Files.h" -#include "vcpkg_Graphs.h" -#include "vcpkg_Util.h" -#include "vcpkglib.h" - -namespace vcpkg::Dependencies -{ - std::vector<PackageSpec> AnyParagraph::dependencies(const Triplet& triplet) const - { - auto to_package_specs = [&](const std::vector<std::string>& dependencies_as_string) { - return Util::fmap(dependencies_as_string, [&](const std::string s) { - return PackageSpec::from_name_and_triplet(s, triplet).value_or_exit(VCPKG_LINE_INFO); - }); - }; - - if (auto p = this->status_paragraph.get()) - { - return to_package_specs(p->package.depends); - } - - if (auto p = this->binary_paragraph.get()) - { - return to_package_specs(p->depends); - } - - if (auto p = this->source_paragraph.get()) - { - return to_package_specs(filter_dependencies(p->depends, triplet)); - } - - Checks::exit_with_message(VCPKG_LINE_INFO, - "Cannot get dependencies because there was none of: source/binary/status paragraphs"); - } - - std::string to_output_string(RequestType request_type, const CStringView s) - { - switch (request_type) - { - case RequestType::AUTO_SELECTED: return Strings::format(" * %s", s); - case RequestType::USER_REQUESTED: return Strings::format(" %s", s); - default: Checks::unreachable(VCPKG_LINE_INFO); - } - } - - InstallPlanAction::InstallPlanAction() - : spec(), any_paragraph(), plan_type(InstallPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) - { - } - - InstallPlanAction::InstallPlanAction(const PackageSpec& spec, - const AnyParagraph& any_paragraph, - const RequestType& request_type) - : InstallPlanAction() - { - this->spec = spec; - this->request_type = request_type; - if (auto p = any_paragraph.status_paragraph.get()) - { - this->plan_type = InstallPlanType::ALREADY_INSTALLED; - this->any_paragraph.status_paragraph = *p; - return; - } - - if (auto p = any_paragraph.binary_paragraph.get()) - { - this->plan_type = InstallPlanType::INSTALL; - this->any_paragraph.binary_paragraph = *p; - return; - } - - if (auto p = any_paragraph.source_paragraph.get()) - { - this->plan_type = InstallPlanType::BUILD_AND_INSTALL; - this->any_paragraph.source_paragraph = *p; - return; - } - - this->plan_type = InstallPlanType::UNKNOWN; - } - - bool InstallPlanAction::compare_by_name(const InstallPlanAction* left, const InstallPlanAction* right) - { - return left->spec.name() < right->spec.name(); - } - - RemovePlanAction::RemovePlanAction() : plan_type(RemovePlanType::UNKNOWN), request_type(RequestType::UNKNOWN) {} - - RemovePlanAction::RemovePlanAction(const PackageSpec& spec, - const RemovePlanType& plan_type, - const RequestType& request_type) - : spec(spec), plan_type(plan_type), request_type(request_type) - { - } - - bool ExportPlanAction::compare_by_name(const ExportPlanAction* left, const ExportPlanAction* right) - { - return left->spec.name() < right->spec.name(); - } - - ExportPlanAction::ExportPlanAction() - : spec(), any_paragraph(), plan_type(ExportPlanType::UNKNOWN), request_type(RequestType::UNKNOWN) - { - } - - ExportPlanAction::ExportPlanAction(const PackageSpec& spec, - const AnyParagraph& any_paragraph, - const RequestType& request_type) - : ExportPlanAction() - { - this->spec = spec; - this->request_type = request_type; - - if (auto p = any_paragraph.binary_paragraph.get()) - { - this->plan_type = ExportPlanType::ALREADY_BUILT; - this->any_paragraph.binary_paragraph = *p; - return; - } - - if (auto p = any_paragraph.source_paragraph.get()) - { - this->plan_type = ExportPlanType::PORT_AVAILABLE_BUT_NOT_BUILT; - this->any_paragraph.source_paragraph = *p; - return; - } - - this->plan_type = ExportPlanType::UNKNOWN; - } - - bool RemovePlanAction::compare_by_name(const RemovePlanAction* left, const RemovePlanAction* right) - { - return left->spec.name() < right->spec.name(); - } - - std::vector<InstallPlanAction> create_install_plan(const VcpkgPaths& paths, - const std::vector<PackageSpec>& specs, - const StatusParagraphs& status_db) - { - struct InstallAdjacencyProvider final : Graphs::AdjacencyProvider<PackageSpec, InstallPlanAction> - { - const VcpkgPaths& paths; - const StatusParagraphs& status_db; - const std::unordered_set<PackageSpec>& specs_as_set; - - InstallAdjacencyProvider(const VcpkgPaths& p, - const StatusParagraphs& s, - const std::unordered_set<PackageSpec>& specs_as_set) - : paths(p), status_db(s), specs_as_set(specs_as_set) - { - } - - std::vector<PackageSpec> adjacency_list(const InstallPlanAction& plan) const override - { - if (plan.any_paragraph.status_paragraph.get()) return std::vector<PackageSpec>{}; - return plan.any_paragraph.dependencies(plan.spec.triplet()); - } - - InstallPlanAction load_vertex_data(const PackageSpec& spec) const override - { - const RequestType request_type = specs_as_set.find(spec) != specs_as_set.end() - ? RequestType::USER_REQUESTED - : RequestType::AUTO_SELECTED; - auto it = status_db.find_installed(spec); - if (it != status_db.end()) return InstallPlanAction{spec, {*it->get(), nullopt, nullopt}, request_type}; - - Expected<BinaryParagraph> maybe_bpgh = Paragraphs::try_load_cached_package(paths, spec); - if (auto bpgh = maybe_bpgh.get()) - return InstallPlanAction{spec, {nullopt, *bpgh, nullopt}, request_type}; - - Expected<SourceParagraph> maybe_spgh = - Paragraphs::try_load_port(paths.get_filesystem(), paths.port_dir(spec)); - if (auto spgh = maybe_spgh.get()) - return InstallPlanAction{spec, {nullopt, nullopt, *spgh}, request_type}; - - Checks::exit_with_message(VCPKG_LINE_INFO, "Could not find package %s", spec); - } - }; - - const std::unordered_set<PackageSpec> specs_as_set(specs.cbegin(), specs.cend()); - std::vector<InstallPlanAction> toposort = - Graphs::topological_sort(specs, InstallAdjacencyProvider{paths, status_db, specs_as_set}); - Util::erase_remove_if(toposort, [](const InstallPlanAction& plan) { - return plan.request_type == RequestType::AUTO_SELECTED && - plan.plan_type == InstallPlanType::ALREADY_INSTALLED; - }); - - return toposort; - } - - std::vector<RemovePlanAction> create_remove_plan(const std::vector<PackageSpec>& specs, - const StatusParagraphs& status_db) - { - struct RemoveAdjacencyProvider final : Graphs::AdjacencyProvider<PackageSpec, RemovePlanAction> - { - const StatusParagraphs& status_db; - const std::vector<StatusParagraph*>& installed_ports; - const std::unordered_set<PackageSpec>& specs_as_set; - - RemoveAdjacencyProvider(const StatusParagraphs& status_db, - const std::vector<StatusParagraph*>& installed_ports, - const std::unordered_set<PackageSpec>& specs_as_set) - : status_db(status_db), installed_ports(installed_ports), specs_as_set(specs_as_set) - { - } - - std::vector<PackageSpec> adjacency_list(const RemovePlanAction& plan) const override - { - if (plan.plan_type == RemovePlanType::NOT_INSTALLED) - { - return {}; - } - - const PackageSpec& spec = plan.spec; - std::vector<PackageSpec> dependents; - for (const StatusParagraph* an_installed_package : installed_ports) - { - if (an_installed_package->package.spec.triplet() != spec.triplet()) continue; - - const std::vector<std::string>& deps = an_installed_package->package.depends; - if (std::find(deps.begin(), deps.end(), spec.name()) == deps.end()) continue; - - dependents.push_back(an_installed_package->package.spec); - } - - return dependents; - } - - RemovePlanAction load_vertex_data(const PackageSpec& spec) const override - { - const RequestType request_type = specs_as_set.find(spec) != specs_as_set.end() - ? RequestType::USER_REQUESTED - : RequestType::AUTO_SELECTED; - const StatusParagraphs::const_iterator it = status_db.find_installed(spec); - if (it == status_db.end()) - { - return RemovePlanAction{spec, RemovePlanType::NOT_INSTALLED, request_type}; - } - return RemovePlanAction{spec, RemovePlanType::REMOVE, request_type}; - } - }; - - const std::vector<StatusParagraph*>& installed_ports = get_installed_ports(status_db); - const std::unordered_set<PackageSpec> specs_as_set(specs.cbegin(), specs.cend()); - return Graphs::topological_sort(specs, RemoveAdjacencyProvider{status_db, installed_ports, specs_as_set}); - } - - std::vector<ExportPlanAction> create_export_plan(const VcpkgPaths& paths, - const std::vector<PackageSpec>& specs, - const StatusParagraphs& status_db) - { - struct ExportAdjacencyProvider final : Graphs::AdjacencyProvider<PackageSpec, ExportPlanAction> - { - const VcpkgPaths& paths; - const StatusParagraphs& status_db; - const std::unordered_set<PackageSpec>& specs_as_set; - - ExportAdjacencyProvider(const VcpkgPaths& p, - const StatusParagraphs& s, - const std::unordered_set<PackageSpec>& specs_as_set) - : paths(p), status_db(s), specs_as_set(specs_as_set) - { - } - - std::vector<PackageSpec> adjacency_list(const ExportPlanAction& plan) const override - { - return plan.any_paragraph.dependencies(plan.spec.triplet()); - } - - ExportPlanAction load_vertex_data(const PackageSpec& spec) const override - { - const RequestType request_type = specs_as_set.find(spec) != specs_as_set.end() - ? RequestType::USER_REQUESTED - : RequestType::AUTO_SELECTED; - - Expected<BinaryParagraph> maybe_bpgh = Paragraphs::try_load_cached_package(paths, spec); - if (auto bpgh = maybe_bpgh.get()) - return ExportPlanAction{spec, {nullopt, *bpgh, nullopt}, request_type}; - - Expected<SourceParagraph> maybe_spgh = - Paragraphs::try_load_port(paths.get_filesystem(), paths.port_dir(spec)); - if (auto spgh = maybe_spgh.get()) - return ExportPlanAction{spec, {nullopt, nullopt, *spgh}, request_type}; - - Checks::exit_with_message(VCPKG_LINE_INFO, "Could not find package %s", spec); - } - }; - - const std::unordered_set<PackageSpec> specs_as_set(specs.cbegin(), specs.cend()); - std::vector<ExportPlanAction> toposort = - Graphs::topological_sort(specs, ExportAdjacencyProvider{paths, status_db, specs_as_set}); - return toposort; - } -} diff --git a/toolsrc/src/vcpkg_Input.cpp b/toolsrc/src/vcpkg_Input.cpp deleted file mode 100644 index 7d8e4767e..000000000 --- a/toolsrc/src/vcpkg_Input.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "pch.h" - -#include "metrics.h" -#include "vcpkg_Commands.h" -#include "vcpkg_Input.h" -#include "vcpkg_System.h" - -namespace vcpkg::Input -{ - PackageSpec check_and_get_package_spec(const std::string& package_spec_as_string, - const Triplet& default_triplet, - CStringView example_text) - { - const std::string as_lowercase = Strings::ascii_to_lowercase(package_spec_as_string); - Expected<PackageSpec> expected_spec = PackageSpec::from_string(as_lowercase, default_triplet); - if (auto spec = expected_spec.get()) - { - return *spec; - } - - // Intentionally show the lowercased string - System::println(System::Color::error, "Error: %s: %s", expected_spec.error_code().message(), as_lowercase); - System::print(example_text); - Checks::exit_fail(VCPKG_LINE_INFO); - } - - void check_triplet(const Triplet& t, const VcpkgPaths& paths) - { - if (!paths.is_valid_triplet(t)) - { - System::println(System::Color::error, "Error: invalid triplet: %s", t); - Metrics::track_property("error", "invalid triplet: " + t.to_string()); - Commands::Help::help_topic_valid_triplet(paths); - Checks::exit_fail(VCPKG_LINE_INFO); - } - } -} diff --git a/toolsrc/src/vcpkg_Strings.cpp b/toolsrc/src/vcpkg_Strings.cpp deleted file mode 100644 index 9ba9eb700..000000000 --- a/toolsrc/src/vcpkg_Strings.cpp +++ /dev/null @@ -1,125 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Strings.h" -#include "vcpkg_Util.h" - -namespace vcpkg::Strings::details -{ - // To disambiguate between two overloads - static const auto isspace = [](const char c) { return std::isspace(c); }; - - // Avoids C4244 warnings because of char<->int conversion that occur when using std::tolower() - static char tolower_char(const char c) { return static_cast<char>(std::tolower(c)); } - - static _locale_t& c_locale() - { - static _locale_t c_locale_impl = _create_locale(LC_ALL, "C"); - return c_locale_impl; - } - - std::string format_internal(const char* fmtstr, ...) - { - va_list lst; - va_start(lst, fmtstr); - - const int sz = _vscprintf_l(fmtstr, c_locale(), lst); - std::string output(sz, '\0'); - _vsnprintf_s_l(&output[0], output.size() + 1, output.size() + 1, fmtstr, c_locale(), lst); - va_end(lst); - - return output; - } - - std::wstring wformat_internal(const wchar_t* fmtstr, ...) - { - va_list lst; - va_start(lst, fmtstr); - - const int sz = _vscwprintf_l(fmtstr, c_locale(), lst); - std::wstring output(sz, '\0'); - _vsnwprintf_s_l(&output[0], output.size() + 1, output.size() + 1, fmtstr, c_locale(), lst); - va_end(lst); - - return output; - } -} - -namespace vcpkg::Strings -{ - std::wstring to_utf16(const CStringView s) - { - std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion; - return conversion.from_bytes(s); - } - - std::string to_utf8(const CWStringView w) - { - std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conversion; - return conversion.to_bytes(w); - } - - std::string::const_iterator case_insensitive_ascii_find(const std::string& s, const std::string& pattern) - { - const std::string pattern_as_lower_case(ascii_to_lowercase(pattern)); - return search(s.begin(), - s.end(), - pattern_as_lower_case.begin(), - pattern_as_lower_case.end(), - [](const char a, const char b) { return details::tolower_char(a) == b; }); - } - - int case_insensitive_ascii_compare(const CStringView left, const CStringView right) - { - return _stricmp(left, right); - } - - std::string ascii_to_lowercase(const std::string& input) - { - std::string output(input); - std::transform(output.begin(), output.end(), output.begin(), &details::tolower_char); - return output; - } - - void trim(std::string* s) - { - s->erase(std::find_if_not(s->rbegin(), s->rend(), details::isspace).base(), s->end()); - s->erase(s->begin(), std::find_if_not(s->begin(), s->end(), details::isspace)); - } - - std::string trimmed(const std::string& s) - { - auto whitespace_back = std::find_if_not(s.rbegin(), s.rend(), details::isspace).base(); - auto whitespace_front = std::find_if_not(s.begin(), whitespace_back, details::isspace); - return std::string(whitespace_front, whitespace_back); - } - - void trim_all_and_remove_whitespace_strings(std::vector<std::string>* strings) - { - for (std::string& s : *strings) - { - trim(&s); - } - - Util::erase_remove_if(*strings, [](const std::string& s) { return s == ""; }); - } - - std::vector<std::string> split(const std::string& s, const std::string& delimiter) - { - std::vector<std::string> output; - - size_t i = 0; - for (size_t pos = s.find(delimiter); pos != std::string::npos; pos = s.find(delimiter, pos)) - { - output.push_back(s.substr(i, pos - i)); - i = ++pos; - } - - // Add the rest of the string after the last delimiter, unless there is nothing after it - if (i != s.length()) - { - output.push_back(s.substr(i, s.length())); - } - - return output; - } -} diff --git a/toolsrc/src/vcpkg_System.cpp b/toolsrc/src/vcpkg_System.cpp deleted file mode 100644 index 21329e003..000000000 --- a/toolsrc/src/vcpkg_System.cpp +++ /dev/null @@ -1,290 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Checks.h" -#include "vcpkg_System.h" -#include "vcpkglib.h" - -namespace vcpkg::System -{ - tm get_current_date_time() - { - using std::chrono::system_clock; - std::time_t now_time = system_clock::to_time_t(system_clock::now()); - tm parts; - localtime_s(&parts, &now_time); - return parts; - } - - 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); - } - - Optional<CPUArchitecture> to_cpu_architecture(CStringView arch) - { - if (Strings::case_insensitive_ascii_compare(arch, "x86") == 0) return CPUArchitecture::X86; - if (Strings::case_insensitive_ascii_compare(arch, "x64") == 0) return CPUArchitecture::X64; - if (Strings::case_insensitive_ascii_compare(arch, "amd64") == 0) return CPUArchitecture::X64; - if (Strings::case_insensitive_ascii_compare(arch, "arm") == 0) return CPUArchitecture::ARM; - if (Strings::case_insensitive_ascii_compare(arch, "arm64") == 0) return CPUArchitecture::ARM64; - return nullopt; - } - - CPUArchitecture get_host_processor() - { - auto w6432 = get_environment_variable(L"PROCESSOR_ARCHITEW6432"); - if (auto p = w6432.get()) return to_cpu_architecture(Strings::to_utf8(*p)).value_or_exit(VCPKG_LINE_INFO); - - auto procarch = get_environment_variable(L"PROCESSOR_ARCHITECTURE").value_or_exit(VCPKG_LINE_INFO); - return to_cpu_architecture(Strings::to_utf8(procarch)).value_or_exit(VCPKG_LINE_INFO); - } - - int cmd_execute_clean(const CWStringView cmd_line) - { - static const std::wstring system_root = get_environment_variable(L"SystemRoot").value_or_exit(VCPKG_LINE_INFO); - static const std::wstring system_32 = system_root + LR"(\system32)"; - static const std::wstring new_PATH = Strings::wformat( - LR"(Path=%s;%s;%s\Wbem;%s\WindowsPowerShell\v1.0\)", system_32, system_root, system_32, system_32); - - std::vector<std::wstring> env_wstrings = { - L"ALLUSERSPROFILE", - L"APPDATA", - L"CommonProgramFiles", - L"CommonProgramFiles(x86)", - L"CommonProgramW6432", - L"COMPUTERNAME", - L"ComSpec", - L"HOMEDRIVE", - L"HOMEPATH", - L"LOCALAPPDATA", - L"LOGONSERVER", - L"NUMBER_OF_PROCESSORS", - L"OS", - L"PATHEXT", - L"PROCESSOR_ARCHITECTURE", - L"PROCESSOR_ARCHITEW6432", - L"PROCESSOR_IDENTIFIER", - L"PROCESSOR_LEVEL", - L"PROCESSOR_REVISION", - L"ProgramData", - L"ProgramFiles", - L"ProgramFiles(x86)", - L"ProgramW6432", - L"PROMPT", - L"PSModulePath", - L"PUBLIC", - L"SystemDrive", - L"SystemRoot", - L"TEMP", - L"TMP", - L"USERDNSDOMAIN", - L"USERDOMAIN", - L"USERDOMAIN_ROAMINGPROFILE", - L"USERNAME", - L"USERPROFILE", - L"windir", - // Enables proxy information to be passed to Curl, the underlying download library in cmake.exe - L"HTTP_PROXY", - L"HTTPS_PROXY", - // Enables find_package(CUDA) in CMake - L"CUDA_PATH", - }; - - // Flush stdout before launching external process - _flushall(); - - std::vector<const wchar_t*> env_cstr; - env_cstr.reserve(env_wstrings.size() + 2); - - for (auto&& env_wstring : env_wstrings) - { - const Optional<std::wstring> value = System::get_environment_variable(env_wstring); - auto v = value.get(); - if (!v || v->empty()) continue; - - env_wstring.push_back(L'='); - env_wstring.append(*v); - env_cstr.push_back(env_wstring.c_str()); - } - - env_cstr.push_back(new_PATH.c_str()); - env_cstr.push_back(nullptr); - - // Basically we are wrapping it in quotes - const std::wstring& actual_cmd_line = Strings::wformat(LR"###("%s")###", cmd_line); - Debug::println("_wspawnlpe(cmd.exe /c %s)", Strings::to_utf8(actual_cmd_line)); - auto exit_code = - _wspawnlpe(_P_WAIT, L"cmd.exe", L"cmd.exe", L"/c", actual_cmd_line.c_str(), nullptr, env_cstr.data()); - Debug::println("_wspawnlpe() returned %d", exit_code); - return static_cast<int>(exit_code); - } - - int cmd_execute(const CWStringView cmd_line) - { - // Flush stdout before launching external process - _flushall(); - - // Basically we are wrapping it in quotes - const std::wstring& actual_cmd_line = Strings::wformat(LR"###("%s")###", cmd_line); - Debug::println("_wsystem(%s)", Strings::to_utf8(actual_cmd_line)); - int exit_code = _wsystem(actual_cmd_line.c_str()); - Debug::println("_wsystem() returned %d", exit_code); - return exit_code; - } - - ExitCodeAndOutput cmd_execute_and_capture_output(const CWStringView cmd_line) - { - // Flush stdout before launching external process - fflush(stdout); - - const std::wstring& actual_cmd_line = Strings::wformat(LR"###("%s 2>&1")###", 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}; - } - - std::wstring create_powershell_script_cmd(const fs::path& script_path, const CWStringView args) - { - // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned - return Strings::wformat( - LR"(powershell -NoProfile -ExecutionPolicy Bypass -Command "& {& '%s' %s}")", script_path.native(), args); - } - - void print(const CStringView message) { fputs(message, stdout); } - - void println(const CStringView message) - { - print(message); - putchar('\n'); - } - - void print(const Color c, const CStringView message) - { - HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); - - CONSOLE_SCREEN_BUFFER_INFO consoleScreenBufferInfo{}; - GetConsoleScreenBufferInfo(hConsole, &consoleScreenBufferInfo); - auto original_color = consoleScreenBufferInfo.wAttributes; - - SetConsoleTextAttribute(hConsole, static_cast<WORD>(c) | (original_color & 0xF0)); - print(message); - SetConsoleTextAttribute(hConsole, original_color); - } - - void println(const Color c, const CStringView message) - { - print(c, message); - putchar('\n'); - } - - Optional<std::wstring> get_environment_variable(const CWStringView varname) noexcept - { - auto sz = GetEnvironmentVariableW(varname, nullptr, 0); - if (sz == 0) return nullopt; - - std::wstring ret(sz, L'\0'); - - Checks::check_exit(VCPKG_LINE_INFO, MAXDWORD >= ret.size()); - auto sz2 = GetEnvironmentVariableW(varname, ret.data(), static_cast<DWORD>(ret.size())); - Checks::check_exit(VCPKG_LINE_INFO, sz2 + 1 == sz); - ret.pop_back(); - return ret; - } - - static bool is_string_keytype(DWORD hkey_type) - { - return hkey_type == REG_SZ || hkey_type == REG_MULTI_SZ || hkey_type == REG_EXPAND_SZ; - } - - Optional<std::wstring> get_registry_string(HKEY base, const CWStringView subKey, const CWStringView valuename) - { - HKEY k = nullptr; - LSTATUS ec = RegOpenKeyExW(base, subKey, NULL, KEY_READ, &k); - if (ec != ERROR_SUCCESS) return nullopt; - - DWORD dwBufferSize = 0; - DWORD dwType = 0; - auto rc = RegQueryValueExW(k, valuename, nullptr, &dwType, nullptr, &dwBufferSize); - if (rc != ERROR_SUCCESS || !is_string_keytype(dwType) || dwBufferSize == 0 || - dwBufferSize % sizeof(wchar_t) != 0) - return nullopt; - std::wstring ret; - ret.resize(dwBufferSize / sizeof(wchar_t)); - - rc = RegQueryValueExW(k, valuename, nullptr, &dwType, reinterpret_cast<LPBYTE>(ret.data()), &dwBufferSize); - if (rc != ERROR_SUCCESS || !is_string_keytype(dwType) || dwBufferSize != sizeof(wchar_t) * ret.size()) - return nullopt; - - ret.pop_back(); // remove extra trailing null byte - return ret; - } - - static const fs::path& get_ProgramFiles() - { - static const fs::path p = System::get_environment_variable(L"PROGRAMFILES").value_or_exit(VCPKG_LINE_INFO); - return p; - } - - const fs::path& get_ProgramFiles_32_bit() - { - static const fs::path p = []() -> fs::path { - auto value = System::get_environment_variable(L"ProgramFiles(x86)"); - if (auto v = value.get()) - { - return std::move(*v); - } - return get_ProgramFiles(); - }(); - return p; - } - - const fs::path& get_ProgramFiles_platform_bitness() - { - static const fs::path p = []() -> fs::path { - auto value = System::get_environment_variable(L"ProgramW6432"); - if (auto v = value.get()) - { - return std::move(*v); - } - return get_ProgramFiles(); - }(); - return p; - } -} - -namespace vcpkg::Debug -{ - void println(const CStringView message) - { - if (g_debugging) - { - System::println("[DEBUG] %s", message); - } - } - - void println(const System::Color c, const CStringView message) - { - if (g_debugging) - { - System::println(c, "[DEBUG] %s", message); - } - } -} diff --git a/toolsrc/src/vcpkglib_helpers.cpp b/toolsrc/src/vcpkglib_helpers.cpp deleted file mode 100644 index 9f992d320..000000000 --- a/toolsrc/src/vcpkglib_helpers.cpp +++ /dev/null @@ -1,56 +0,0 @@ -#include "pch.h" - -#include "vcpkg_Checks.h" -#include "vcpkglib_helpers.h" - -namespace vcpkg::details -{ - std::string optional_field(const std::unordered_map<std::string, std::string>& fields, const std::string& fieldname) - { - auto it = fields.find(fieldname); - if (it == fields.end()) - { - return std::string(); - } - - return it->second; - } - - std::string remove_optional_field(std::unordered_map<std::string, std::string>* fields, - const std::string& fieldname) - { - auto it = fields->find(fieldname); - if (it == fields->end()) - { - return std::string(); - } - - const std::string value = std::move(it->second); - fields->erase(it); - return value; - } - - std::string required_field(const std::unordered_map<std::string, std::string>& fields, const std::string& fieldname) - { - auto it = fields.find(fieldname); - Checks::check_exit(VCPKG_LINE_INFO, it != fields.end(), "Required field not present: %s", fieldname); - return it->second; - } - - std::string remove_required_field(std::unordered_map<std::string, std::string>* fields, - const std::string& fieldname) - { - auto it = fields->find(fieldname); - Checks::check_exit(VCPKG_LINE_INFO, it != fields->end(), "Required field not present: %s", fieldname); - - const std::string value = std::move(it->second); - fields->erase(it); - return value; - } - - std::string shorten_description(const std::string& desc) - { - auto simple_desc = std::regex_replace(desc, std::regex("\\s+"), " "); - return simple_desc.size() <= 52 ? simple_desc : simple_desc.substr(0, 49) + "..."; - } -} diff --git a/toolsrc/src/vcpkg_metrics_uploader.cpp b/toolsrc/src/vcpkgmetricsuploader.cpp index 38bf7ff9c..2239fe750 100644 --- a/toolsrc/src/vcpkg_metrics_uploader.cpp +++ b/toolsrc/src/vcpkgmetricsuploader.cpp @@ -1,6 +1,8 @@ -#include "metrics.h" -#include "vcpkg_Checks.h" -#include "vcpkg_Files.h" +#include <vcpkg/metrics.h> + +#include <vcpkg/base/checks.h> +#include <vcpkg/base/files.h> + #include <Windows.h> using namespace vcpkg; @@ -11,5 +13,8 @@ int WINAPI WinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int) LPWSTR* szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount); Checks::check_exit(VCPKG_LINE_INFO, argCount == 2, "Requires exactly one argument, the path to the payload file"); - Metrics::upload(Files::get_real_filesystem().read_contents(szArgList[1]).value_or_exit(VCPKG_LINE_INFO)); + auto v = Files::get_real_filesystem().read_contents(szArgList[1]).value_or_exit(VCPKG_LINE_INFO); + Metrics::g_metrics.lock()->upload(v); + LocalFree(szArgList); + return 0; } |
