diff options
| author | Robert Schumacher <roschuma@microsoft.com> | 2017-02-08 15:12:28 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-02-08 15:12:28 -0800 |
| commit | 7ddae17e2f520e83d25f78c078bf8b8a58fff447 (patch) | |
| tree | 87e2fc5c57a685367ec051b1efbdeb5d3ab43f4d /toolsrc/src | |
| parent | 5e588ddb5be9e6e27cebcc3be2e1a27f3ca83a50 (diff) | |
| parent | a9f7fc6e90feaad50c1221ef9bd56e2620302215 (diff) | |
| download | vcpkg-7ddae17e2f520e83d25f78c078bf8b8a58fff447.tar.gz vcpkg-7ddae17e2f520e83d25f78c078bf8b8a58fff447.zip | |
Merge branch 'master' into master
Diffstat (limited to 'toolsrc/src')
62 files changed, 3869 insertions, 2065 deletions
diff --git a/toolsrc/src/BinaryParagraph.cpp b/toolsrc/src/BinaryParagraph.cpp index 48d04f686..f949677a3 100644 --- a/toolsrc/src/BinaryParagraph.cpp +++ b/toolsrc/src/BinaryParagraph.cpp @@ -1,3 +1,4 @@ +#include "pch.h" #include "BinaryParagraph.h" #include "vcpkglib_helpers.h" #include "vcpkg_Checks.h" @@ -6,28 +7,57 @@ 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"; + } + + static const std::vector<std::string>& get_list_of_valid_fields() + { + static const std::vector<std::string> valid_fields = + { + BinaryParagraphRequiredField::PACKAGE, + BinaryParagraphRequiredField::VERSION, + BinaryParagraphRequiredField::ARCHITECTURE, + + BinaryParagraphOptionalField::DESCRIPTION, + BinaryParagraphOptionalField::MAINTAINER, + BinaryParagraphOptionalField::DEPENDS + }; + + return valid_fields; + } + BinaryParagraph::BinaryParagraph() = default; - BinaryParagraph::BinaryParagraph(const std::unordered_map<std::string, std::string>& fields) : - version(required_field(fields, "Version")), - description(optional_field(fields, "Description")), - maintainer(optional_field(fields, "Maintainer")) + BinaryParagraph::BinaryParagraph(std::unordered_map<std::string, std::string> fields) { - const std::string name = required_field(fields, "Package"); - const triplet target_triplet = triplet::from_canonical_name(required_field(fields, "Architecture")); + const std::string name = details::remove_required_field(&fields, BinaryParagraphRequiredField::PACKAGE); + const std::string architecture = details::remove_required_field(&fields, BinaryParagraphRequiredField::ARCHITECTURE); + const triplet target_triplet = triplet::from_canonical_name(architecture); + this->spec = package_spec::from_name_and_triplet(name, target_triplet).get_or_throw(); + this->version = details::remove_required_field(&fields, BinaryParagraphRequiredField::VERSION); - { - std::string multi_arch = required_field(fields, "Multi-Arch"); - Checks::check_throw(multi_arch == "same", "Multi-Arch must be 'same' but was %s", multi_arch); - } + this->description = details::remove_optional_field(&fields, BinaryParagraphOptionalField::DESCRIPTION); + this->maintainer = details::remove_optional_field(&fields, BinaryParagraphOptionalField::MAINTAINER); - std::string deps = optional_field(fields, "Depends"); - if (!deps.empty()) - { - this->depends.clear(); - this->depends = parse_depends(deps); - } + std::string multi_arch = details::remove_required_field(&fields, BinaryParagraphRequiredField::MULTI_ARCH); + Checks::check_exit(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& target_triplet) @@ -36,12 +66,12 @@ namespace vcpkg this->version = spgh.version; this->description = spgh.description; this->maintainer = spgh.maintainer; - this->depends = spgh.depends; + this->depends = filter_dependencies(spgh.depends, target_triplet); } std::string BinaryParagraph::displayname() const { - return Strings::format("%s:%s", this->spec.name(), this->spec.target_triplet()); + return this->spec.display_name(); } std::string BinaryParagraph::dir() const diff --git a/toolsrc/src/MachineType.cpp b/toolsrc/src/MachineType.cpp new file mode 100644 index 000000000..81012234d --- /dev/null +++ b/toolsrc/src/MachineType.cpp @@ -0,0 +1,42 @@ +#include "pch.h" +#include "MachineType.h" +#include "vcpkg_Checks.h" + +namespace vcpkg +{ + MachineType getMachineType(const uint16_t value) + { + MachineType t = static_cast<MachineType>(value); + switch (t) + { + case MachineType::UNKNOWN: + case MachineType::AM33: + case MachineType::AMD64: + case MachineType::ARM: + case MachineType::ARM64: + case MachineType::ARMNT: + case MachineType::EBC: + case MachineType::I386: + case MachineType::IA64: + case MachineType::M32R: + case MachineType::MIPS16: + case MachineType::MIPSFPU: + case MachineType::MIPSFPU16: + case MachineType::POWERPC: + case MachineType::POWERPCFP: + case MachineType::R4000: + case MachineType::RISCV32: + case MachineType::RISCV64: + case MachineType::RISCV128: + case MachineType::SH3: + case MachineType::SH3DSP: + case MachineType::SH4: + case MachineType::SH5: + case MachineType::THUMB: + case MachineType::WCEMIPSV2: + return t; + default: + Checks::exit_with_message("Unknown machine type code 0x%x", value); + } + } +} diff --git a/toolsrc/src/Paragraphs.cpp b/toolsrc/src/Paragraphs.cpp new file mode 100644 index 000000000..6dde5da7c --- /dev/null +++ b/toolsrc/src/Paragraphs.cpp @@ -0,0 +1,170 @@ +#include "pch.h" +#include "Paragraphs.h" +#include "vcpkg_Files.h" + +namespace vcpkg::Paragraphs +{ + struct Parser + { + Parser(const char* c, const char* e) : cur(c), end(e) + { + } + + private: + const char* cur; + const char* const end; + + void peek(char& ch) const + { + if (cur == end) + ch = 0; + else + ch = *cur; + } + + void next(char& ch) + { + if (cur == end) + ch = 0; + else + { + ++cur; + peek(ch); + } + } + + void skip_spaces(char& ch) + { + while (ch == ' ' || ch == '\t') + next(ch); + } + + static bool is_alphanum(char ch) + { + return (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9'); + } + + static bool is_lineend(char ch) + { + return ch == '\r' || ch == '\n' || ch == 0; + } + + void get_fieldvalue(char& ch, std::string& fieldvalue) + { + fieldvalue.clear(); + + auto beginning_of_line = cur; + do + { + // scan to end of current line (it is part of the field value) + while (!is_lineend(ch)) + next(ch); + + fieldvalue.append(beginning_of_line, cur); + + if (ch == '\r') + next(ch); + if (ch == '\n') + next(ch); + + if (is_alphanum(ch)) + { + // Line begins a new field. + return; + } + + beginning_of_line = cur; + + // Line may continue the current field with data or terminate the paragraph, + // depending on first nonspace character. + skip_spaces(ch); + + if (is_lineend(ch)) + { + // Line was whitespace or empty. + // This terminates the field and the paragraph. + // We leave the blank line's whitespace consumed, because it doesn't matter. + return; + } + + // First nonspace is not a newline. This continues the current field value. + // We forcibly convert all newlines into single '\n' for ease of text handling later on. + fieldvalue.push_back('\n'); + } + while (true); + } + + void get_fieldname(char& ch, std::string& fieldname) + { + auto begin_fieldname = cur; + while (is_alphanum(ch) || ch == '-') + next(ch); + Checks::check_throw(ch == ':', "Expected ':'"); + fieldname = std::string(begin_fieldname, cur); + + // skip ': ' + next(ch); + skip_spaces(ch); + } + + void get_paragraph(char& ch, std::unordered_map<std::string, std::string>& fields) + { + fields.clear(); + std::string fieldname; + std::string fieldvalue; + do + { + get_fieldname(ch, fieldname); + + auto it = fields.find(fieldname); + Checks::check_throw(it == fields.end(), "Duplicate field"); + + get_fieldvalue(ch, fieldvalue); + + fields.emplace(fieldname, fieldvalue); + } + while (!is_lineend(ch)); + } + + public: + std::vector<std::unordered_map<std::string, std::string>> get_paragraphs() + { + std::vector<std::unordered_map<std::string, std::string>> paragraphs; + + char ch; + peek(ch); + + while (ch != 0) + { + if (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t') + { + next(ch); + continue; + } + + paragraphs.emplace_back(); + get_paragraph(ch, paragraphs.back()); + } + + return paragraphs; + } + }; + + std::vector<std::unordered_map<std::string, std::string>> get_paragraphs(const fs::path& control_path) + { + const expected<std::string> contents = Files::read_contents(control_path); + if (auto spgh = contents.get()) + { + return parse_paragraphs(*spgh); + } + + Checks::exit_with_message("Error while reading %s: %s", control_path.generic_string(), contents.error_code().message()); + } + + std::vector<std::unordered_map<std::string, std::string>> parse_paragraphs(const std::string& str) + { + return Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); + } +} diff --git a/toolsrc/src/PostBuildLint.cpp b/toolsrc/src/PostBuildLint.cpp new file mode 100644 index 000000000..90bd55843 --- /dev/null +++ b/toolsrc/src/PostBuildLint.cpp @@ -0,0 +1,730 @@ +#include "pch.h" +#include "vcpkg_paths.h" +#include "package_spec.h" +#include "vcpkg_Files.h" +#include "vcpkg_System.h" +#include "vcpkg_Environment.h" +#include "coff_file_reader.h" +#include "PostBuildLint_BuildInfo.h" +#include "PostBuildLint_BuildType.h" + +namespace vcpkg::PostBuildLint +{ + enum class lint_status + { + SUCCESS = 0, + ERROR_DETECTED = 1 + }; + + struct OutdatedDynamicCrt + { + std::string name; + std::regex regex; + + OutdatedDynamicCrt(const std::string& name, const std::string& regex_as_string) + : name(name), regex(std::regex(regex_as_string, std::regex_constants::icase)) {} + }; + + 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; + } + + static lint_status check_for_files_in_include_directory(const fs::path& package_dir) + { + 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."); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_files_in_debug_include_directory(const fs::path& package_dir) + { + const fs::path debug_include_dir = package_dir / "debug" / "include"; + std::vector<fs::path> files_found; + + Files::recursive_find_matching_paths_in_dir(debug_include_dir, [&](const fs::path& current) + { + return !fs::is_directory(current) && current.extension() != ".ifc"; + }, &files_found); + + if (!files_found.empty()) + { + System::println(System::color::warning, "Include files should not be duplicated into the /debug/include directory. If this cannot be disabled in the project cmake, use\n" + " file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)" + ); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_files_in_debug_share_directory(const fs::path& package_dir) + { + const fs::path debug_share = package_dir / "debug" / "share"; + + if (fs::exists(debug_share) && !fs::is_empty(debug_share)) + { + System::println(System::color::warning, "No files should be present in /debug/share"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_folder_lib_cmake(const fs::path& package_dir) + { + const fs::path lib_cmake = package_dir / "lib" / "cmake"; + if (fs::exists(lib_cmake)) + { + System::println(System::color::warning, "The /lib/cmake folder should be moved to just /cmake"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_misplaced_cmake_files(const fs::path& package_dir, const package_spec& spec) + { + std::vector<fs::path> misplaced_cmake_files; + Files::recursive_find_files_with_extension_in_dir(package_dir / "cmake", ".cmake", &misplaced_cmake_files); + Files::recursive_find_files_with_extension_in_dir(package_dir / "debug" / "cmake", ".cmake", &misplaced_cmake_files); + Files::recursive_find_files_with_extension_in_dir(package_dir / "lib" / "cmake", ".cmake", &misplaced_cmake_files); + Files::recursive_find_files_with_extension_in_dir(package_dir / "debug" / "lib" / "cmake", ".cmake", &misplaced_cmake_files); + + if (!misplaced_cmake_files.empty()) + { + System::println(System::color::warning, "The following cmake files were found outside /share/%s. Please place cmake files in /share/%s.", spec.name(), spec.name()); + Files::print_paths(misplaced_cmake_files); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_folder_debug_lib_cmake(const fs::path& package_dir) + { + const fs::path lib_cmake_debug = package_dir / "debug" / "lib" / "cmake"; + if (fs::exists(lib_cmake_debug)) + { + System::println(System::color::warning, "The /debug/lib/cmake folder should be moved to just /debug/cmake"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_dlls_in_lib_dirs(const fs::path& package_dir) + { + std::vector<fs::path> dlls; + Files::recursive_find_files_with_extension_in_dir(package_dir / "lib", ".dll", &dlls); + Files::recursive_find_files_with_extension_in_dir(package_dir / "debug" / "lib", ".dll", &dlls); + + if (!dlls.empty()) + { + System::println(System::color::warning, "\nThe following dlls were found in /lib and /debug/lib. Please move them to /bin or /debug/bin, respectively."); + Files::print_paths(dlls); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_for_copyright_file(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path packages_dir = paths.packages / spec.dir(); + const fs::path copyright_file = packages_dir / "share" / spec.name() / "copyright"; + if (fs::exists(copyright_file)) + { + return lint_status::SUCCESS; + } + const fs::path current_buildtrees_dir = paths.buildtrees / spec.name(); + const fs::path current_buildtrees_dir_src = current_buildtrees_dir / "src"; + + std::vector<fs::path> potential_copyright_files; + // Only searching one level deep + for (auto it = fs::recursive_directory_iterator(current_buildtrees_dir_src); it != fs::recursive_directory_iterator(); ++it) + { + if (it.depth() > 1) + { + continue; + } + + const std::string filename = it->path().filename().string(); + if (filename == "LICENSE" || filename == "LICENSE.txt" || filename == "COPYING") + { + potential_copyright_files.push_back(it->path()); + } + } + + System::println(System::color::warning, "The software license must be available at ${CURRENT_PACKAGES_DIR}/share/%s/copyright .", spec.name()); + if (potential_copyright_files.size() == 1) // if there is only one candidate, provide the cmake lines needed to place it in the proper location + { + const fs::path found_file = potential_copyright_files[0]; + const fs::path relative_path = found_file.string().erase(0, current_buildtrees_dir.string().size() + 1); // The +1 is needed to remove the "/" + System::println("\n file(COPY ${CURRENT_BUILDTREES_DIR}/%s DESTINATION ${CURRENT_PACKAGES_DIR}/share/%s)\n" + " file(RENAME ${CURRENT_PACKAGES_DIR}/share/%s/%s ${CURRENT_PACKAGES_DIR}/share/%s/copyright)", + relative_path.generic_string(), spec.name(), spec.name(), found_file.filename().generic_string(), spec.name()); + return lint_status::ERROR_DETECTED; + } + + if (potential_copyright_files.size() > 1) + { + System::println(System::color::warning, "The following files are potential copyright files:"); + Files::print_paths(potential_copyright_files); + } + + System::println(" %s/share/%s/copyright", packages_dir.generic_string(), spec.name()); + return lint_status::ERROR_DETECTED; + } + + static lint_status check_for_exes(const fs::path& package_dir) + { + std::vector<fs::path> exes; + Files::recursive_find_files_with_extension_in_dir(package_dir / "bin", ".exe", &exes); + Files::recursive_find_files_with_extension_in_dir(package_dir / "debug" / "bin", ".exe", &exes); + + if (!exes.empty()) + { + System::println(System::color::warning, "The following EXEs were found in /bin and /debug/bin. EXEs are not valid distribution targets."); + Files::print_paths(exes); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_exports_of_dlls(const std::vector<fs::path>& dlls, const fs::path& dumpbin_exe) + { + 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()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + if (ec_data.output.find("ordinal hint RVA name") == std::string::npos) + { + dlls_with_no_exports.push_back(dll); + } + } + + if (!dlls_with_no_exports.empty()) + { + System::println(System::color::warning, "The following DLLs have no exports:"); + Files::print_paths(dlls_with_no_exports); + System::println(System::color::warning, "DLLs without any exports are likely a bug in the build script."); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_uwp_bit_of_dlls(const std::string& expected_system_name, const std::vector<fs::path>& dlls, const fs::path dumpbin_exe) + { + if (expected_system_name != "uwp") + { + return lint_status::SUCCESS; + } + + std::vector<fs::path> dlls_with_improper_uwp_bit; + for (const fs::path& dll : dlls) + { + const std::wstring cmd_line = Strings::wformat(LR"("%s" /headers "%s")", dumpbin_exe.native(), dll.native()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + if (ec_data.output.find("App Container") == std::string::npos) + { + dlls_with_improper_uwp_bit.push_back(dll); + } + } + + if (!dlls_with_improper_uwp_bit.empty()) + { + System::println(System::color::warning, "The following DLLs do not have the App Container bit set:"); + Files::print_paths(dlls_with_improper_uwp_bit); + System::println(System::color::warning, "This bit is required for Windows Store apps."); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + struct file_and_arch + { + fs::path file; + std::string actual_arch; + }; + + static std::string get_actual_architecture(const MachineType& machine_type) + { + switch (machine_type) + { + case MachineType::AMD64: + case MachineType::IA64: + return "x64"; + case MachineType::I386: + return "x86"; + case MachineType::ARM: + case MachineType::ARMNT: + return "arm"; + default: + return "Machine Type Code = " + std::to_string(static_cast<uint16_t>(machine_type)); + } + } + + static void print_invalid_architecture_files(const std::string& expected_architecture, std::vector<file_and_arch> binaries_with_invalid_architecture) + { + System::println(System::color::warning, "The following files were built for an incorrect architecture:"); + System::println(""); + for (const file_and_arch& b : binaries_with_invalid_architecture) + { + System::println(" %s", b.file.generic_string()); + System::println("Expected %s, but was: %s", expected_architecture, b.actual_arch); + System::println(""); + } + } + + static lint_status check_dll_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) + { + std::vector<file_and_arch> binaries_with_invalid_architecture; + + for (const fs::path& file : files) + { + Checks::check_exit(file.extension() == ".dll", "The file extension was not .dll: %s", file.generic_string()); + COFFFileReader::dll_info info = COFFFileReader::read_dll(file); + const std::string actual_architecture = get_actual_architecture(info.machine_type); + + if (expected_architecture != actual_architecture) + { + binaries_with_invalid_architecture.push_back({file, actual_architecture}); + } + } + + if (!binaries_with_invalid_architecture.empty()) + { + print_invalid_architecture_files(expected_architecture, binaries_with_invalid_architecture); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_lib_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) + { + std::vector<file_and_arch> binaries_with_invalid_architecture; + + for (const fs::path& file : files) + { + Checks::check_exit(file.extension() == ".lib", "The file extension was not .lib: %s", file.generic_string()); + COFFFileReader::lib_info info = COFFFileReader::read_lib(file); + Checks::check_exit(info.machine_types.size() == 1, "Found more than 1 architecture in file %s", file.generic_string()); + + const std::string actual_architecture = get_actual_architecture(info.machine_types.at(0)); + if (expected_architecture != actual_architecture) + { + binaries_with_invalid_architecture.push_back({file, actual_architecture}); + } + } + + if (!binaries_with_invalid_architecture.empty()) + { + print_invalid_architecture_files(expected_architecture, binaries_with_invalid_architecture); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_no_dlls_present(const std::vector<fs::path>& dlls) + { + if (dlls.empty()) + { + return lint_status::SUCCESS; + } + + System::println(System::color::warning, "DLLs should not be present in a static build, but the following DLLs were found:"); + Files::print_paths(dlls); + return lint_status::ERROR_DETECTED; + } + + static lint_status check_matching_debug_and_release_binaries(const std::vector<fs::path>& debug_binaries, const std::vector<fs::path>& release_binaries) + { + const size_t debug_count = debug_binaries.size(); + const size_t release_count = release_binaries.size(); + if (debug_count == release_count) + { + return lint_status::SUCCESS; + } + + System::println(System::color::warning, "Mismatching number of debug and release binaries. Found %d for debug but %d for release.", debug_count, release_count); + System::println("Debug binaries"); + Files::print_paths(debug_binaries); + + System::println("Release binaries"); + Files::print_paths(release_binaries); + + if (debug_count == 0) + { + System::println(System::color::warning, "Debug binaries were not found"); + } + if (release_count == 0) + { + System::println(System::color::warning, "Release binaries were not found"); + } + + System::println(""); + + return lint_status::ERROR_DETECTED; + } + + static lint_status check_lib_files_are_available_if_dlls_are_available(const std::map<BuildPolicies::type, opt_bool_t>& policies, const size_t lib_count, const size_t dll_count, const fs::path& lib_dir) + { + auto it = policies.find(BuildPolicies::DLLS_WITHOUT_LIBS); + if (it != policies.cend() && it->second == opt_bool_t::DISABLED) + { + return lint_status::SUCCESS; + } + + if (lib_count == 0 && dll_count != 0) + { + System::println(System::color::warning, "Import libs were not present in %s", lib_dir.generic_string()); + System::println(System::color::warning, + "If this is intended, add the following line in the portfile:\n" + " SET(%s disabled)", BuildPolicies::DLLS_WITHOUT_LIBS.cmake_variable()); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_no_subdirectories(const fs::path& dir) + { + const std::vector<fs::path> subdirectories = Files::recursive_find_matching_paths_in_dir(dir, [&](const fs::path& current) + { + return fs::is_directory(current); + }); + + if (!subdirectories.empty()) + { + System::println(System::color::warning, "Directory %s should have no subdirectories", dir.generic_string()); + System::println("The following subdirectories were found: "); + Files::print_paths(subdirectories); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_bin_folders_are_not_present_in_static_build(const fs::path& package_dir) + { + const fs::path bin = package_dir / "bin"; + const fs::path debug_bin = package_dir / "debug" / "bin"; + + if (!fs::exists(bin) && !fs::exists(debug_bin)) + { + return lint_status::SUCCESS; + } + + if (fs::exists(bin)) + { + System::println(System::color::warning, R"(There should be no bin\ directory in a static build, but %s is present.)", bin.generic_string()); + } + + if (fs::exists(debug_bin)) + { + System::println(System::color::warning, R"(There should be no debug\bin\ directory in a static build, but %s is present.)", debug_bin.generic_string()); + } + + System::println(System::color::warning, R"(If the creation of bin\ and/or debug\bin\ cannot be disabled, use this in the portfile to remove them)" "\n" + "\n" + R"###( if(VCPKG_LIBRARY_LINKAGE STREQUAL static))###""\n" + R"###( file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin))###""\n" + R"###( endif())###" + "\n" + ); + + return lint_status::ERROR_DETECTED; + } + + static lint_status check_no_empty_folders(const fs::path& dir) + { + const std::vector<fs::path> empty_directories = Files::recursive_find_matching_paths_in_dir(dir, [](const fs::path& current) + { + return fs::is_directory(current) && fs::is_empty(current); + }); + + if (!empty_directories.empty()) + { + System::println(System::color::warning, "There should be no empty directories in %s", dir.generic_string()); + System::println("The following empty directories were found: "); + Files::print_paths(empty_directories); + System::println(System::color::warning, "If a directory should be populated but is not, this might indicate an error in the portfile.\n" + "If the directories are not needed and their creation cannot be disabled, use something like this in the portfile to remove them)\n" + "\n" + R"###( file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/a/dir ${CURRENT_PACKAGES_DIR}/some/other/dir))###""\n" + "\n"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + struct BuildType_and_file + { + fs::path file; + BuildType build_type; + }; + + static lint_status check_crt_linkage_of_libs(const BuildType& expected_build_type, const std::vector<fs::path>& libs, const fs::path dumpbin_exe) + { + std::vector<BuildType> bad_build_types = BuildType::values(); + 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; + + for (const fs::path& lib : libs) + { + const std::wstring cmd_line = Strings::wformat(LR"("%s" /directives "%s")", dumpbin_exe.native(), lib.native()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + for (const BuildType& bad_build_type : bad_build_types) + { + if (std::regex_search(ec_data.output.cbegin(), ec_data.output.cend(), bad_build_type.crt_regex())) + { + libs_with_invalid_crt.push_back({lib, bad_build_type}); + break; + } + } + } + + if (!libs_with_invalid_crt.empty()) + { + System::println(System::color::warning, "Expected %s crt linkage, but the following libs had invalid crt linkage:", expected_build_type.toString()); + System::println(""); + for (const BuildType_and_file btf : libs_with_invalid_crt) + { + System::println(" %s: %s", btf.file.generic_string(), btf.build_type.toString()); + } + System::println(""); + + System::println(System::color::warning, "To inspect the lib files, use:\n dumpbin.exe /directives mylibfile.lib"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + struct OutdatedDynamicCrt_and_file + { + fs::path file; + OutdatedDynamicCrt outdated_crt; + }; + + static lint_status check_outdated_crt_linkage_of_dlls(const std::vector<fs::path>& dlls, const fs::path dumpbin_exe) + { + const std::vector<OutdatedDynamicCrt>& outdated_crts = get_outdated_dynamic_crts(); + + std::vector<OutdatedDynamicCrt_and_file> 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()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + for (const OutdatedDynamicCrt& outdated_crt : outdated_crts) + { + if (std::regex_search(ec_data.output.cbegin(), ec_data.output.cend(), outdated_crt.regex)) + { + dlls_with_outdated_crt.push_back({dll, outdated_crt}); + break; + } + } + } + + 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(" %s: %s", btf.file.generic_string(), btf.outdated_crt.name); + } + System::println(""); + + System::println(System::color::warning, "To inspect the dll files, use:\n dumpbin.exe /dependents mydllfile.dll"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static lint_status check_no_files_in_package_dir_and_debug_dir(const fs::path& package_dir) + { + std::vector<fs::path> misplaced_files; + + Files::non_recursive_find_matching_paths_in_dir(package_dir, [](const fs::path& current) + { + const std::string filename = current.filename().generic_string(); + return !fs::is_directory(current) && !((_stricmp(filename.c_str(), "CONTROL") == 0 || _stricmp(filename.c_str(), "BUILD_INFO") == 0)); + }, &misplaced_files); + + const fs::path debug_dir = package_dir / "debug"; + Files::non_recursive_find_all_files_in_dir(debug_dir, &misplaced_files); + + if (!misplaced_files.empty()) + { + System::println(System::color::warning, "The following files are placed in\n%s and\n%s: ", package_dir.generic_string(), debug_dir.generic_string()); + Files::print_paths(misplaced_files); + System::println(System::color::warning, "Files cannot be present in those directories.\n"); + return lint_status::ERROR_DETECTED; + } + + return lint_status::SUCCESS; + } + + static void operator +=(size_t& left, const lint_status& right) + { + left += static_cast<size_t>(right); + } + + static size_t perform_all_checks_and_return_error_count(const package_spec& spec, const vcpkg_paths& paths) + { + const fs::path dumpbin_exe = Environment::get_dumpbin_exe(paths); + + BuildInfo build_info = read_build_info(paths.build_info_file_path(spec)); + const fs::path package_dir = paths.package_dir(spec); + + size_t error_count = 0; + + auto it = build_info.policies.find(BuildPolicies::EMPTY_PACKAGE); + if (it != build_info.policies.cend() && it->second == opt_bool_t::ENABLED) + { + return error_count; + } + + error_count += check_for_files_in_include_directory(package_dir); + error_count += check_for_files_in_debug_include_directory(package_dir); + error_count += check_for_files_in_debug_share_directory(package_dir); + error_count += check_folder_lib_cmake(package_dir); + error_count += check_for_misplaced_cmake_files(package_dir, spec); + error_count += check_folder_debug_lib_cmake(package_dir); + error_count += check_for_dlls_in_lib_dirs(package_dir); + error_count += check_for_copyright_file(spec, paths); + error_count += check_for_exes(package_dir); + + const fs::path debug_lib_dir = package_dir / "debug" / "lib"; + const fs::path release_lib_dir = package_dir / "lib"; + const fs::path debug_bin_dir = package_dir / "debug" / "bin"; + const fs::path release_bin_dir = package_dir / "bin"; + + const std::vector<fs::path> debug_libs = Files::recursive_find_files_with_extension_in_dir(debug_lib_dir, ".lib"); + const std::vector<fs::path> release_libs = Files::recursive_find_files_with_extension_in_dir(release_lib_dir, ".lib"); + + error_count += check_matching_debug_and_release_binaries(debug_libs, release_libs); + + std::vector<fs::path> libs; + libs.insert(libs.cend(), debug_libs.cbegin(), debug_libs.cend()); + libs.insert(libs.cend(), release_libs.cbegin(), release_libs.cend()); + + error_count += check_lib_architecture(spec.target_triplet().architecture(), libs); + + switch (linkage_type_value_of(build_info.library_linkage)) + { + case LinkageType::DYNAMIC: + { + const std::vector<fs::path> debug_dlls = Files::recursive_find_files_with_extension_in_dir(debug_bin_dir, ".dll"); + const std::vector<fs::path> release_dlls = Files::recursive_find_files_with_extension_in_dir(release_bin_dir, ".dll"); + + 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); + error_count += check_lib_files_are_available_if_dlls_are_available(build_info.policies, release_libs.size(), release_dlls.size(), release_lib_dir); + + std::vector<fs::path> dlls; + 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, dumpbin_exe); + error_count += check_uwp_bit_of_dlls(spec.target_triplet().system(), dlls, dumpbin_exe); + error_count += check_dll_architecture(spec.target_triplet().architecture(), dlls); + + error_count += check_outdated_crt_linkage_of_dlls(dlls, dumpbin_exe); + break; + } + case LinkageType::STATIC: + { + std::vector<fs::path> dlls; + Files::recursive_find_files_with_extension_in_dir(package_dir, ".dll", &dlls); + error_count += check_no_dlls_present(dlls); + + error_count += check_bin_folders_are_not_present_in_static_build(package_dir); + + error_count += check_crt_linkage_of_libs(BuildType::value_of(ConfigurationType::DEBUG, linkage_type_value_of(build_info.crt_linkage)), debug_libs, dumpbin_exe); + error_count += check_crt_linkage_of_libs(BuildType::value_of(ConfigurationType::RELEASE, linkage_type_value_of(build_info.crt_linkage)), release_libs, dumpbin_exe); + break; + } + case LinkageType::UNKNOWN: + { + error_count += 1; + System::println(System::color::warning, "Unknown library_linkage architecture: [ %s ]", build_info.library_linkage); + break; + } + default: + Checks::unreachable(); + } +#if 0 + error_count += check_no_subdirectories(package_dir / "lib"); + error_count += check_no_subdirectories(package_dir / "debug" / "lib"); +#endif + + error_count += check_no_empty_folders(package_dir); + error_count += check_no_files_in_package_dir_and_debug_dir(package_dir); + + return error_count; + } + + void perform_all_checks(const package_spec& spec, const vcpkg_paths& paths) + { + System::println("-- Performing post-build validation"); + + const size_t error_count = perform_all_checks_and_return_error_count(spec, paths); + + if (error_count != 0) + { + const fs::path portfile = paths.ports / spec.name() / "portfile.cmake"; + System::println(System::color::error, "Found %u error(s). Please correct the portfile:\n %s", error_count, portfile.string()); + exit(EXIT_FAILURE); + } + + System::println("-- Performing post-build validation done"); + } +} diff --git a/toolsrc/src/PostBuildLint_BuildInfo.cpp b/toolsrc/src/PostBuildLint_BuildInfo.cpp new file mode 100644 index 000000000..63107acd1 --- /dev/null +++ b/toolsrc/src/PostBuildLint_BuildInfo.cpp @@ -0,0 +1,42 @@ +#include "pch.h" +#include "PostBuildLint_BuildInfo.h" +#include "vcpkg_Checks.h" +#include "opt_bool.h" +#include "vcpkglib_helpers.h" + +namespace vcpkg::PostBuildLint +{ + // + namespace BuildInfoRequiredField + { + static const std::string CRT_LINKAGE = "CRTLinkage"; + static const std::string LIBRARY_LINKAGE = "LibraryLinkage"; + } + + BuildInfo BuildInfo::create(std::unordered_map<std::string, std::string> pgh) + { + BuildInfo build_info; + build_info.crt_linkage = details::remove_required_field(&pgh, BuildInfoRequiredField::CRT_LINKAGE); + build_info.library_linkage = details::remove_required_field(&pgh, BuildInfoRequiredField::LIBRARY_LINKAGE); + + // The remaining entries are policies + for (const std::unordered_map<std::string, std::string>::value_type& p : pgh) + { + const BuildPolicies::type policy = BuildPolicies::parse(p.first); + Checks::check_exit(policy != BuildPolicies::UNKNOWN, "Unknown policy found: %s", p.first); + const opt_bool_t status = opt_bool::parse(p.second); + build_info.policies.emplace(policy, status); + } + + return build_info; + } + + BuildInfo read_build_info(const fs::path& filepath) + { + const std::vector<std::unordered_map<std::string, std::string>> pghs = Paragraphs::get_paragraphs(filepath); + Checks::check_exit(pghs.size() == 1, "Invalid BUILD_INFO file for package"); + + return BuildInfo::create(pghs[0]); + } + +} diff --git a/toolsrc/src/PostBuildLint_BuildPolicies.cpp b/toolsrc/src/PostBuildLint_BuildPolicies.cpp new file mode 100644 index 000000000..4e5ac3cea --- /dev/null +++ b/toolsrc/src/PostBuildLint_BuildPolicies.cpp @@ -0,0 +1,66 @@ +#include "pch.h" +#include "PostBuildLint_BuildPolicies.h" +#include "vcpkg_Checks.h" + +namespace vcpkg::PostBuildLint::BuildPolicies +{ + static const std::string NAME_UNKNOWN = "PolicyUnknown"; + static const std::string NAME_EMPTY_PACKAGE = "PolicyEmptyPackage"; + static const std::string NAME_DLLS_WITHOUT_LIBS = "PolicyDLLsWithoutLIBs"; + + const std::string& type::toString() const + { + switch (this->backing_enum) + { + case EMPTY_PACKAGE: + return NAME_EMPTY_PACKAGE; + case DLLS_WITHOUT_LIBS: + return NAME_DLLS_WITHOUT_LIBS; + case UNKNOWN: + return NAME_UNKNOWN; + default: + Checks::unreachable(); + } + } + + const std::string& type::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"; + + switch (this->backing_enum) + { + case EMPTY_PACKAGE: + return CMAKE_VARIABLE_EMPTY_PACKAGE; + case DLLS_WITHOUT_LIBS: + return CMAKE_VARIABLE_DLLS_WITHOUT_LIBS; + case UNKNOWN: + Checks::exit_with_message("No CMake command corresponds to UNKNOWN"); + default: + Checks::unreachable(); + } + } + + type::type(): backing_enum(backing_enum_t::UNKNOWN) {} + + const std::vector<type>& values() + { + static const std::vector<type>& v = {UNKNOWN, EMPTY_PACKAGE, DLLS_WITHOUT_LIBS}; + return v; + } + + type parse(const std::string& s) + { + if (s == NAME_EMPTY_PACKAGE) + { + return BuildPolicies::EMPTY_PACKAGE; + } + + if (s == NAME_DLLS_WITHOUT_LIBS) + { + return BuildPolicies::DLLS_WITHOUT_LIBS; + } + + return BuildPolicies::UNKNOWN; + } +} diff --git a/toolsrc/src/PostBuildLint_BuildType.cpp b/toolsrc/src/PostBuildLint_BuildType.cpp new file mode 100644 index 000000000..b4e199aee --- /dev/null +++ b/toolsrc/src/PostBuildLint_BuildType.cpp @@ -0,0 +1,68 @@ +#include "pch.h" +#include "PostBuildLint_BuildType.h" +#include "vcpkg_Checks.h" + +namespace vcpkg::PostBuildLint +{ + const BuildType BuildType::DEBUG_STATIC = BuildType(ConfigurationType::DEBUG, LinkageType::STATIC, R"(/DEFAULTLIB:LIBCMTD)"); + const BuildType BuildType::DEBUG_DYNAMIC = BuildType(ConfigurationType::DEBUG, LinkageType::DYNAMIC, R"(/DEFAULTLIB:MSVCRTD)"); + const BuildType BuildType::RELEASE_STATIC = BuildType(ConfigurationType::RELEASE, LinkageType::STATIC, R"(/DEFAULTLIB:LIBCMT[^D])"); + const BuildType BuildType::RELEASE_DYNAMIC = BuildType(ConfigurationType::RELEASE, LinkageType::DYNAMIC, R"(/DEFAULTLIB:MSVCRT[^D])"); + + BuildType BuildType::value_of(const ConfigurationType& config, const LinkageType& linkage) + { + if (config == ConfigurationType::DEBUG && linkage == LinkageType::STATIC) + { + return DEBUG_STATIC; + } + + if (config == ConfigurationType::DEBUG && linkage == LinkageType::DYNAMIC) + { + return DEBUG_DYNAMIC; + } + + if (config == ConfigurationType::RELEASE && linkage == LinkageType::STATIC) + { + return RELEASE_STATIC; + } + + if (config == ConfigurationType::RELEASE && linkage == LinkageType::DYNAMIC) + { + return RELEASE_DYNAMIC; + } + + Checks::unreachable(); + } + + const ConfigurationType& BuildType::config() const + { + return this->m_config; + } + + const LinkageType& BuildType::linkage() const + { + return this->m_linkage; + } + + std::regex BuildType::crt_regex() const + { + const std::regex r(this->m_crt_regex_as_string, std::regex_constants::icase); + return r; + } + + std::string BuildType::toString() const + { + const std::string s = Strings::format("[%s,%s]", to_string(this->m_config), to_string(this->m_linkage)); + return s; + } + + bool operator==(const BuildType& lhs, const BuildType& rhs) + { + return lhs.config() == rhs.config() && lhs.linkage() == rhs.linkage(); + } + + bool operator!=(const BuildType& lhs, const BuildType& rhs) + { + return !(lhs == rhs); + } +} diff --git a/toolsrc/src/PostBuildLint_ConfigurationType.cpp b/toolsrc/src/PostBuildLint_ConfigurationType.cpp new file mode 100644 index 000000000..9c3499cac --- /dev/null +++ b/toolsrc/src/PostBuildLint_ConfigurationType.cpp @@ -0,0 +1,19 @@ +#include "pch.h" +#include "PostBuildLint_ConfigurationType.h" +#include "vcpkg_Checks.h" + +namespace vcpkg::PostBuildLint +{ + std::string to_string(const ConfigurationType& conf) + { + switch (conf) + { + case ConfigurationType::DEBUG: + return "Debug"; + case ConfigurationType::RELEASE: + return "Release"; + default: + Checks::unreachable(); + } + } +} diff --git a/toolsrc/src/PostBuildLint_LinkageType.cpp b/toolsrc/src/PostBuildLint_LinkageType.cpp new file mode 100644 index 000000000..8a3f35be8 --- /dev/null +++ b/toolsrc/src/PostBuildLint_LinkageType.cpp @@ -0,0 +1,34 @@ +#include "pch.h" +#include "PostBuildLint_LinkageType.h" +#include "vcpkg_Checks.h" + +namespace vcpkg::PostBuildLint +{ + LinkageType linkage_type_value_of(const std::string& as_string) + { + if (as_string == "dynamic") + { + return LinkageType::DYNAMIC; + } + + if (as_string == "static") + { + return LinkageType::STATIC; + } + + return LinkageType::UNKNOWN; + } + + std::string to_string(const LinkageType& build_info) + { + switch (build_info) + { + case LinkageType::STATIC: + return "static"; + case LinkageType::DYNAMIC: + return "dynamic"; + default: + Checks::unreachable(); + } + } +} diff --git a/toolsrc/src/SourceParagraph.cpp b/toolsrc/src/SourceParagraph.cpp index 374121ae9..8bfe8e84d 100644 --- a/toolsrc/src/SourceParagraph.cpp +++ b/toolsrc/src/SourceParagraph.cpp @@ -1,20 +1,145 @@ +#include "pch.h" #include "SourceParagraph.h" #include "vcpkglib_helpers.h" +#include "vcpkg_System.h" +#include "vcpkg_Maps.h" +#include "triplet.h" -using namespace vcpkg::details; +namespace vcpkg +{ + // + namespace SourceParagraphRequiredField + { + static const std::string SOURCE = "Source"; + static const std::string VERSION = "Version"; + } -vcpkg::SourceParagraph::SourceParagraph() = default; + namespace SourceParagraphOptionalField + { + static const std::string DESCRIPTION = "Description"; + static const std::string MAINTAINER = "Maintainer"; + static const std::string BUILD_DEPENDS = "Build-Depends"; + } -vcpkg::SourceParagraph::SourceParagraph(const std::unordered_map<std::string, std::string>& fields): - name(required_field(fields, "Source")), - version(required_field(fields, "Version")), - description(optional_field(fields, "Description")), - maintainer(optional_field(fields, "Maintainer")) -{ - std::string deps = optional_field(fields, "Build-Depends"); - if (!deps.empty()) + 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::Joiner::on("\n ").join(remaining_fields); + const std::string valid_fields_as_string = Strings::Joiner::on("\n ").join(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); + exit(EXIT_FAILURE); + } + } + + 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; + } + + std::ostream & operator<<(std::ostream & os, const dependency & p) { - this->depends.clear(); - this->depends = parse_depends(deps); - }; + os << p.name; + return os; + } } diff --git a/toolsrc/src/StatusParagraph.cpp b/toolsrc/src/StatusParagraph.cpp index 5aa425969..3f07689ca 100644 --- a/toolsrc/src/StatusParagraph.cpp +++ b/toolsrc/src/StatusParagraph.cpp @@ -1,3 +1,4 @@ +#include "pch.h" #include "StatusParagraph.h" #include "vcpkglib_helpers.h" @@ -5,6 +6,12 @@ using namespace vcpkg::details; namespace vcpkg { + // + namespace BinaryParagraphRequiredField + { + static const std::string STATUS = "Status"; + } + StatusParagraph::StatusParagraph() : want(want_t::error), state(install_state_t::error) { } @@ -19,7 +26,7 @@ namespace vcpkg StatusParagraph::StatusParagraph(const std::unordered_map<std::string, std::string>& fields) : package(fields) { - std::string status_field = required_field(fields, "Status"); + std::string status_field = required_field(fields, BinaryParagraphRequiredField::STATUS); auto b = status_field.begin(); auto mark = b; diff --git a/toolsrc/src/StatusParagraphs.cpp b/toolsrc/src/StatusParagraphs.cpp index 3e23c519a..c2398d2b8 100644 --- a/toolsrc/src/StatusParagraphs.cpp +++ b/toolsrc/src/StatusParagraphs.cpp @@ -1,5 +1,5 @@ +#include "pch.h" #include "StatusParagraphs.h" -#include <algorithm> #include "vcpkg_Checks.h" namespace vcpkg diff --git a/toolsrc/src/Stopwatch.cpp b/toolsrc/src/Stopwatch.cpp index 550f1ebd8..ec7af60b6 100644 --- a/toolsrc/src/Stopwatch.cpp +++ b/toolsrc/src/Stopwatch.cpp @@ -1,3 +1,4 @@ +#include "pch.h" #include "Stopwatch.h" #include "vcpkg_Checks.h" @@ -88,7 +89,7 @@ namespace vcpkg return Strings::format("%.4g ns", nanos_as_double); } - Stopwatch::Stopwatch() : m_isRunning(false), m_elapsedNanos(), m_startTick() + Stopwatch::Stopwatch() : m_isRunning(false), m_elapsedNanos(0), m_startTick() { } diff --git a/toolsrc/src/coff_file_reader.cpp b/toolsrc/src/coff_file_reader.cpp index 593bb18d1..f48f912c1 100644 --- a/toolsrc/src/coff_file_reader.cpp +++ b/toolsrc/src/coff_file_reader.cpp @@ -1,14 +1,10 @@ +#include "pch.h" #include "coff_file_reader.h" -#include <iostream> -#include <cstdint> -#include <algorithm> #include "vcpkg_Checks.h" -#include <set> -#include <fstream> using namespace std; -namespace vcpkg {namespace COFFFileReader +namespace vcpkg::COFFFileReader { template <class T> static T reinterpret_bytes(const char* data) @@ -34,9 +30,9 @@ namespace vcpkg {namespace COFFFileReader return data; } - static void verify_equal_strings(const char* expected, const char* actual, int size) + static void verify_equal_strings(const char* expected, const char* actual, int size, const char* label) { - Checks::check_exit(memcmp(expected, actual, size) == 0, "Incorrect string found. Expected: %s but found %s", expected, actual); + Checks::check_exit(memcmp(expected, actual, size) == 0, "Incorrect string (%s) found. Expected: (%s) but found (%s)", label, expected, actual); } static void read_and_verify_PE_signature(fstream& fs) @@ -52,17 +48,17 @@ namespace vcpkg {namespace COFFFileReader fs.seekg(offset_to_PE_signature); char signature[PE_SIGNATURE_SIZE]; fs.read(signature, PE_SIGNATURE_SIZE); - verify_equal_strings(PE_SIGNATURE, signature, PE_SIGNATURE_SIZE); + verify_equal_strings(PE_SIGNATURE, signature, PE_SIGNATURE_SIZE, "PE_SIGNATURE"); fs.seekg(offset_to_PE_signature + PE_SIGNATURE_SIZE, ios_base::beg); } - static fpos_t align_to(const fpos_t unaligned_offset, const int alignment_size) + static fpos_t align_to_size(const uint64_t unaligned, const uint64_t alignment_size) { - fpos_t aligned_offset = unaligned_offset - 1; - aligned_offset /= alignment_size; - aligned_offset += 1; - aligned_offset *= alignment_size; - return aligned_offset; + fpos_t aligned = unaligned - 1; + aligned /= alignment_size; + aligned += 1; + aligned *= alignment_size; + return aligned; } struct coff_file_header @@ -77,14 +73,6 @@ namespace vcpkg {namespace COFFFileReader return ret; } - static coff_file_header peek(fstream& fs) - { - auto original_pos = fs.tellg().seekpos(); - coff_file_header ret = read(fs); - fs.seekg(original_pos); - return ret; - } - MachineType machineType() const { static const size_t MACHINE_TYPE_OFFSET = 0; @@ -113,8 +101,11 @@ namespace vcpkg {namespace COFFFileReader ret.data.resize(HEADER_SIZE); fs.read(&ret.data[0], HEADER_SIZE); - const std::string header_end = ret.data.substr(HEADER_END_OFFSET, HEADER_END_SIZE); - verify_equal_strings(HEADER_END, header_end.c_str(), HEADER_END_SIZE); + if (ret.data[0] != '\0') // Due to freeglut. github issue #223 + { + const std::string header_end = ret.data.substr(HEADER_END_OFFSET, HEADER_END_SIZE); + verify_equal_strings(HEADER_END, header_end.c_str(), HEADER_END_SIZE, "LIB HEADER_END"); + } return ret; } @@ -128,17 +119,53 @@ namespace vcpkg {namespace COFFFileReader uint64_t member_size() const { + static const size_t ALIGNMENT_SIZE = 2; + static const size_t HEADER_SIZE_OFFSET = 48; static const 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); - return value; + + const uint64_t aligned = align_to_size(value, ALIGNMENT_SIZE); + return aligned; } std::string data; }; + struct offsets_array + { + static offsets_array read(fstream& fs, const uint32_t offset_count) + { + static const size_t OFFSET_WIDTH = 4; + + std::string raw_offsets; + const size_t raw_offset_size = offset_count * OFFSET_WIDTH; + raw_offsets.resize(raw_offset_size); + fs.read(&raw_offsets[0], raw_offset_size); + + offsets_array 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()); + + // Ignore offsets that point to offset 0. See vcpkg github #223 #288 #292 + if (value != 0) + { + ret.data.push_back(value); + } + } + + // Sort the offsets, because it is possible for them to be unsorted. See vcpkg github #292 + std::sort(ret.data.begin(), ret.data.end()); + return ret; + } + + std::vector<uint32_t> data; + }; + struct import_header { static const size_t HEADER_SIZE = 20; @@ -168,14 +195,6 @@ namespace vcpkg {namespace COFFFileReader return ret; } - static import_header peek(fstream& fs) - { - auto original_pos = fs.tellg().seekpos(); - import_header ret = read(fs); - fs.seekg(original_pos); - return ret; - } - MachineType machineType() const { static const size_t MACHINE_TYPE_OFFSET = 6; @@ -190,14 +209,6 @@ namespace vcpkg {namespace COFFFileReader std::string data; }; - static void skip_archive_member(fstream& fs, uint64_t member_size) - { - static const size_t ALIGNMENT_SIZE = 2; - - const fpos_t new_offset = align_to(member_size, ALIGNMENT_SIZE); - fs.seekg(new_offset, ios_base::cur); - } - static void read_and_verify_archive_file_signature(fstream& fs) { static const char* FILE_START = "!<arch>\n"; @@ -207,10 +218,10 @@ namespace vcpkg {namespace COFFFileReader char file_start[FILE_START_SIZE]; fs.read(file_start, FILE_START_SIZE); - verify_equal_strings(FILE_START, file_start, FILE_START_SIZE); + verify_equal_strings(FILE_START, file_start, FILE_START_SIZE, "LIB FILE_START"); } - dll_info read_dll(const fs::path path) + dll_info read_dll(const fs::path& path) { std::fstream fs(path, std::ios::in | std::ios::binary | std::ios::ate); Checks::check_exit(fs.is_open(), "Could not open file %s for reading", path.generic_string()); @@ -221,43 +232,76 @@ namespace vcpkg {namespace COFFFileReader return {machine}; } - lib_info read_lib(const fs::path path) + struct marker_t + { + 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 seek_to_marker(fstream& fs) const + { + fs.seekg(this->m_absolute_position, ios_base::beg); + } + + void advance_by(const uint64_t offset) + { + this->m_absolute_position += offset; + } + + private: + fpos_t m_absolute_position = 0; + }; + + lib_info read_lib(const fs::path& path) { std::fstream fs(path, std::ios::in | std::ios::binary | std::ios::ate); Checks::check_exit(fs.is_open(), "Could not open file %s for reading", path.generic_string()); read_and_verify_archive_file_signature(fs); + marker_t marker; + marker.set_to_current_pos(fs); + // First Linker Member const archive_member_header first_linker_member_header = archive_member_header::read(fs); Checks::check_exit(first_linker_member_header.name().substr(0, 2) == "/ ", "Could not find proper first linker member"); - skip_archive_member(fs, first_linker_member_header.member_size()); + marker.advance_by(archive_member_header::HEADER_SIZE + first_linker_member_header.member_size()); + marker.seek_to_marker(fs); const archive_member_header second_linker_member_header = archive_member_header::read(fs); Checks::check_exit(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 = peek_value_from_stream<uint32_t>(fs); - skip_archive_member(fs, second_linker_member_header.member_size()); + const uint32_t archive_member_count = read_value_from_stream<uint32_t>(fs); + const offsets_array offsets = offsets_array::read(fs, archive_member_count); + marker.advance_by(archive_member_header::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 archive_member_header longnames_member_header = archive_member_header::read(fs); - skip_archive_member(fs, longnames_member_header.member_size()); + marker.advance_by(archive_member_header::HEADER_SIZE + longnames_member_header.member_size()); + marker.seek_to_marker(fs); } std::set<MachineType> machine_types; // Next we have the obj and pseudo-object files - for (uint32_t i = 0; i < archive_member_count; i++) + for (const uint32_t offset : offsets.data) { - const archive_member_header header = archive_member_header::read(fs); + marker.set_to_offset(offset + archive_member_header::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 MachineType machine = isImportHeader ? import_header::peek(fs).machineType() : coff_file_header::peek(fs).machineType(); + const MachineType machine = isImportHeader ? import_header::read(fs).machineType() : coff_file_header::read(fs).machineType(); machine_types.insert(machine); - skip_archive_member(fs, header.member_size()); } return {std::vector<MachineType>(machine_types.cbegin(), machine_types.cend())}; } -}} +} diff --git a/toolsrc/src/commands_available_commands.cpp b/toolsrc/src/commands_available_commands.cpp new file mode 100644 index 000000000..56056218b --- /dev/null +++ b/toolsrc/src/commands_available_commands.cpp @@ -0,0 +1,45 @@ +#include "pch.h" +#include "vcpkg_Commands.h" + +namespace vcpkg::Commands +{ + const std::vector<package_name_and_function<command_type_a>>& get_available_commands_type_a() + { + static std::vector<package_name_and_function<command_type_a>> t = { + {"install", &Install::perform_and_exit}, + {"remove", &Remove::perform_and_exit}, + {"build", &Build::perform_and_exit}, + {"build_external", &BuildExternal::perform_and_exit} + }; + return t; + } + + const std::vector<package_name_and_function<command_type_b>>& get_available_commands_type_b() + { + static std::vector<package_name_and_function<command_type_b>> t = { + {"/?", &Help::perform_and_exit}, + {"help", &Help::perform_and_exit}, + {"search", &Search::perform_and_exit}, + {"list", &List::perform_and_exit}, + {"integrate", &Integrate::perform_and_exit}, + {"owns", &Owns::perform_and_exit}, + {"update", &Update::perform_and_exit}, + {"edit", &Edit::perform_and_exit}, + {"create", &Create::perform_and_exit}, + {"import", &Import::perform_and_exit}, + {"cache", &Cache::perform_and_exit}, + {"portsdiff", &PortsDiff::perform_and_exit} + }; + return t; + } + + const std::vector<package_name_and_function<command_type_c>>& get_available_commands_type_c() + { + static std::vector<package_name_and_function<command_type_c>> t = { + {"version", &Version::perform_and_exit}, + {"contact", &Contact::perform_and_exit}, + {"hash", &Hash::perform_and_exit}, + }; + return t; + } +} diff --git a/toolsrc/src/commands_build.cpp b/toolsrc/src/commands_build.cpp new file mode 100644 index 000000000..c5a278450 --- /dev/null +++ b/toolsrc/src/commands_build.cpp @@ -0,0 +1,131 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "StatusParagraphs.h" +#include "vcpkglib.h" +#include "vcpkg_Input.h" +#include "PostBuildLint.h" +#include "vcpkg_Dependencies.h" +#include "vcpkg_System.h" +#include "vcpkg_Environment.h" +#include "metrics.h" +#include "vcpkg_info.h" + +namespace vcpkg::Commands::Build +{ + using Dependencies::package_spec_with_install_plan; + using Dependencies::install_plan_type; + + static const std::string OPTION_CHECKS_ONLY = "--checks-only"; + + static void create_binary_control_file(const vcpkg_paths& paths, const SourceParagraph& source_paragraph, const triplet& target_triplet) + { + const BinaryParagraph bpgh = BinaryParagraph(source_paragraph, target_triplet); + const fs::path binary_control_file = paths.packages / bpgh.dir() / "CONTROL"; + std::ofstream(binary_control_file) << bpgh; + } + + void build_package(const SourceParagraph& source_paragraph, const package_spec& spec, const vcpkg_paths& paths, const fs::path& port_dir) + { + Checks::check_exit(spec.name() == source_paragraph.name, "inconsistent arguments to build_internal()"); + const triplet& target_triplet = spec.target_triplet(); + + const fs::path ports_cmake_script_path = paths.ports_cmake; + const Environment::vcvarsall_and_platform_toolset vcvarsall_bat = Environment::get_vcvarsall_bat(paths); + const std::wstring command = Strings::wformat(LR"("%s" %s >nul 2>&1 && cmake -DCMD=BUILD -DPORT=%s -DTARGET_TRIPLET=%s -DVCPKG_PLATFORM_TOOLSET=%s "-DCURRENT_PORT_DIR=%s/." -P "%s")", + vcvarsall_bat.path.native(), + Strings::utf8_to_utf16(target_triplet.architecture()), + Strings::utf8_to_utf16(source_paragraph.name), + Strings::utf8_to_utf16(target_triplet.canonical_name()), + vcvarsall_bat.platform_toolset, + port_dir.generic_wstring(), + ports_cmake_script_path.generic_wstring()); + + System::Stopwatch2 timer; + timer.start(); + int return_code = System::cmd_execute(command); + timer.stop(); + TrackMetric("buildtimeus-" + spec.toString(), timer.microseconds()); + + if (return_code != 0) + { + System::println(System::color::error, "Error: building package %s failed", spec.toString()); + System::println("Please ensure sure 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.toString(), Info::version()); + TrackProperty("error", "build failed"); + TrackProperty("build_error", spec.toString()); + exit(EXIT_FAILURE); + } + + PostBuildLint::perform_all_checks(spec, paths); + + create_binary_control_file(paths, source_paragraph, target_triplet); + + // const fs::path port_buildtrees_dir = paths.buildtrees / spec.name; + // delete_directory(port_buildtrees_dir); + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + static const std::string example = Commands::Help::create_example_string("build zlib:x64-windows"); + + // Installing multiple packages leads to unintuitive behavior if one of them depends on another. + // Allowing only 1 package for now. + + args.check_exact_arg_count(1, example); + + StatusParagraphs status_db = database_load_check(paths); + + const package_spec spec = Input::check_and_get_package_spec(args.command_arguments.at(0), default_target_triplet, example); + Input::check_triplet(spec.target_triplet(), paths); + + const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments({OPTION_CHECKS_ONLY}); + if (options.find(OPTION_CHECKS_ONLY) != options.end()) + { + PostBuildLint::perform_all_checks(spec, paths); + exit(EXIT_SUCCESS); + } + + // Explicitly load and use the portfile's build dependencies when resolving the build command (instead of a cached package's dependencies). + const expected<SourceParagraph> maybe_spgh = try_load_port(paths, spec.name()); + Checks::check_exit(!maybe_spgh.error_code(), "Could not find package named %s: %s", spec, maybe_spgh.error_code().message()); + const SourceParagraph& spgh = *maybe_spgh.get(); + + const std::vector<std::string> first_level_deps = filter_dependencies(spgh.depends, spec.target_triplet()); + + std::vector<package_spec> first_level_deps_specs; + for (const std::string& dep : first_level_deps) + { + first_level_deps_specs.push_back(package_spec::from_name_and_triplet(dep, spec.target_triplet()).get_or_throw()); + } + + std::vector<package_spec_with_install_plan> unmet_dependencies = Dependencies::create_install_plan(paths, first_level_deps_specs, status_db); + unmet_dependencies.erase( + std::remove_if(unmet_dependencies.begin(), unmet_dependencies.end(), [](const package_spec_with_install_plan& p) + { + return p.plan.plan_type == install_plan_type::ALREADY_INSTALLED; + }), + unmet_dependencies.end()); + + if (!unmet_dependencies.empty()) + { + 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 package_spec_with_install_plan& p : unmet_dependencies) + { + System::println(" %s", p.spec.toString()); + } + System::println(""); + exit(EXIT_FAILURE); + } + + Environment::ensure_utilities_on_path(paths); + build_package(spgh, spec, paths, paths.port_dir(spec)); + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/commands_build_external.cpp b/toolsrc/src/commands_build_external.cpp new file mode 100644 index 000000000..5c3fa9857 --- /dev/null +++ b/toolsrc/src/commands_build_external.cpp @@ -0,0 +1,33 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "vcpkg_System.h" +#include "vcpkg_Environment.h" +#include "vcpkg_Input.h" +#include "vcpkglib.h" + +namespace vcpkg::Commands::BuildExternal +{ + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_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); + + expected<package_spec> maybe_current_spec = package_spec::from_string(args.command_arguments[0], default_target_triplet); + if (auto spec = maybe_current_spec.get()) + { + Input::check_triplet(spec->target_triplet(), paths); + Environment::ensure_utilities_on_path(paths); + const fs::path port_dir = args.command_arguments.at(1); + const expected<SourceParagraph> maybe_spgh = try_load_port(port_dir); + if (auto spgh = maybe_spgh.get()) + { + Commands::Build::build_package(*spgh, *spec, paths, port_dir); + exit(EXIT_SUCCESS); + } + } + + System::println(System::color::error, "Error: %s: %s", maybe_current_spec.error_code().message(), args.command_arguments[0]); + Commands::Help::print_example(Strings::format("%s zlib:x64-windows", args.command)); + exit(EXIT_FAILURE); + } +} diff --git a/toolsrc/src/commands_cache.cpp b/toolsrc/src/commands_cache.cpp index 0d70f0f29..fa49a647f 100644 --- a/toolsrc/src/commands_cache.cpp +++ b/toolsrc/src/commands_cache.cpp @@ -1,36 +1,73 @@ +#include "pch.h" #include "vcpkg_Commands.h" #include "vcpkg_System.h" #include "vcpkg_Files.h" -#include "vcpkg.h" +#include "Paragraphs.h" +#include "BinaryParagraph.h" -namespace vcpkg +namespace vcpkg::Commands::Cache { - void cache_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + static std::vector<BinaryParagraph> read_all_binary_paragraphs(const vcpkg_paths& paths) { - args.check_exact_arg_count(0); + std::vector<BinaryParagraph> output; + for (auto it = fs::directory_iterator(paths.packages); it != fs::directory_iterator(); ++it) + { + const fs::path& path = it->path(); + + try + { + auto file_contents = Files::read_contents(path / "CONTROL"); + if (auto text = file_contents.get()) + { + auto pghs = Paragraphs::parse_paragraphs(*text); + if (pghs.size() != 1) + continue; + + const BinaryParagraph binary_paragraph = BinaryParagraph(pghs[0]); + output.push_back(binary_paragraph); + } + } + catch (std::runtime_error const&) + { + } + } - auto begin_it = fs::directory_iterator(paths.packages); - auto end_it = fs::directory_iterator(); + return output; + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& 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); - if (begin_it == end_it) + const std::vector<BinaryParagraph> binary_paragraphs = read_all_binary_paragraphs(paths); + if (binary_paragraphs.empty()) { System::println("No packages are cached."); exit(EXIT_SUCCESS); } - for (; begin_it != end_it; ++begin_it) + if (args.command_arguments.size() == 0) { - const auto& path = begin_it->path(); - - auto file_contents = Files::get_contents(path / "CONTROL"); - if (auto text = file_contents.get()) + for (const BinaryParagraph& binary_paragraph : binary_paragraphs) + { + const std::string displayname = binary_paragraph.displayname(); + System::println(displayname); + } + } + else + { + // At this point there is 1 argument + for (const BinaryParagraph& binary_paragraph : binary_paragraphs) { - auto pghs = parse_paragraphs(*text); - if (pghs.size() != 1) + const std::string displayname = binary_paragraph.displayname(); + if (Strings::case_insensitive_ascii_find(displayname, args.command_arguments[0]) == displayname.end()) + { continue; + } - auto src = BinaryParagraph(pghs[0]); - System::println(src.displayname().c_str()); + System::println(displayname); } } diff --git a/toolsrc/src/commands_contact.cpp b/toolsrc/src/commands_contact.cpp new file mode 100644 index 000000000..2be468fb8 --- /dev/null +++ b/toolsrc/src/commands_contact.cpp @@ -0,0 +1,14 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "vcpkg_System.h" +#include "vcpkg_info.h" + +namespace vcpkg::Commands::Contact +{ + void perform_and_exit(const vcpkg_cmd_arguments& args) + { + args.check_exact_arg_count(0); + System::println("Send an email to %s with any feedback.", Info::email()); + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/commands_create.cpp b/toolsrc/src/commands_create.cpp index d1611eb5c..5042ab97b 100644 --- a/toolsrc/src/commands_create.cpp +++ b/toolsrc/src/commands_create.cpp @@ -1,16 +1,17 @@ +#include "pch.h" #include "vcpkg_Commands.h" #include "vcpkg_System.h" #include "vcpkg_Environment.h" #include "vcpkg_Files.h" #include "vcpkg_Input.h" -namespace vcpkg +namespace vcpkg::Commands::Create { - void create_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) { - static const std::string example = create_example_string(R"###(create zlib2 http://zlib.net/zlib128.zip "zlib128-2.zip")###"); - args.check_max_arg_count(3, example.c_str()); - args.check_min_arg_count(2, example.c_str()); + static const std::string example = Commands::Help::create_example_string(R"###(create zlib2 http://zlib.net/zlib128.zip "zlib128-2.zip")###"); + args.check_max_arg_count(3, example); + args.check_min_arg_count(2, example); const std::string port_name = args.command_arguments.at(0); Environment::ensure_utilities_on_path(paths); diff --git a/toolsrc/src/commands_edit.cpp b/toolsrc/src/commands_edit.cpp index f07a15875..dff30f7ad 100644 --- a/toolsrc/src/commands_edit.cpp +++ b/toolsrc/src/commands_edit.cpp @@ -1,16 +1,18 @@ +#include "pch.h" #include "vcpkg_Commands.h" #include "vcpkg_System.h" #include "vcpkg_Input.h" -namespace vcpkg +namespace vcpkg::Commands::Edit { - void edit_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) { - static const std::string example = create_example_string("edit zlib"); - args.check_exact_arg_count(1, example.c_str()); + static const std::string example = Commands::Help::create_example_string("edit zlib"); + args.check_exact_arg_count(1, example); const std::string port_name = args.command_arguments.at(0); const fs::path portpath = paths.ports / port_name; + Checks::check_exit(fs::is_directory(portpath), R"(Could not find port named "%s")", port_name); // Find editor std::wstring env_EDITOR = System::wdupenv_str(L"EDITOR"); @@ -27,7 +29,7 @@ namespace vcpkg } } - std::wstring cmdLine = Strings::wformat(LR"("%s" "%s" "%s")", env_EDITOR, portpath.native(), (portpath / "portfile.cmake").native()); + std::wstring cmdLine = Strings::wformat(LR"("%s" "%s" "%s" -n)", env_EDITOR, portpath.native(), (portpath / "portfile.cmake").native()); exit(System::cmd_execute(cmdLine)); } } diff --git a/toolsrc/src/commands_hash.cpp b/toolsrc/src/commands_hash.cpp new file mode 100644 index 000000000..4c0028f53 --- /dev/null +++ b/toolsrc/src/commands_hash.cpp @@ -0,0 +1,45 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "vcpkg_System.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.c_str()); + auto ec_data = System::cmd_execute_and_capture_output(cmd_line); + Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); + + std::string const& output = ec_data.output; + + auto start = output.find_first_of("\r\n"); + Checks::check_exit(start != std::string::npos, "Unexpected output format from command: %s", Strings::utf16_to_utf8(cmd_line)); + + auto end = output.find_first_of("\r\n", start + 1); + Checks::check_exit(end != std::string::npos, "Unexpected output format from command: %s", Strings::utf16_to_utf8(cmd_line)); + + auto hash = output.substr(start, end - start); + hash.erase(std::remove_if(hash.begin(), hash.end(), isspace), hash.end()); + System::println(hash); + } + + void perform_and_exit(const vcpkg_cmd_arguments& 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); + + 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::utf8_to_utf16(args.command_arguments[1])); + } + + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/commands_help.cpp b/toolsrc/src/commands_help.cpp index 194e809b1..6068c22fb 100644 --- a/toolsrc/src/commands_help.cpp +++ b/toolsrc/src/commands_help.cpp @@ -1,20 +1,70 @@ +#include "pch.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" #include "vcpkg_System.h" -namespace vcpkg +namespace vcpkg::Commands::Help { - void version_command(const vcpkg_cmd_arguments& args) + void help_topic_valid_triplet(const vcpkg_paths& paths) { - args.check_exact_arg_count(0); - System::println("Vcpkg package management program version %s\n" - "\n" - "See LICENSE.txt for license information.", vcpkg::version() - ); - exit(EXIT_SUCCESS); + System::println("Available architecture triplets:"); + auto it = fs::directory_iterator(paths.triplets); + for (; it != fs::directory_iterator(); ++it) + { + System::println(" %s", it->path().stem().filename().string()); + } } - void help_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + 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 --purge <pkg> Uninstall and delete a package. \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 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 vcpkg_cmd_arguments& args, const vcpkg_paths& paths) { args.check_max_arg_count(1); if (args.command_arguments.empty()) @@ -35,21 +85,4 @@ namespace vcpkg } exit(EXIT_SUCCESS); } - - void contact_command(const vcpkg_cmd_arguments& args) - { - args.check_exact_arg_count(0); - System::println("Send an email to vcpkg@microsoft.com with any feedback."); - exit(EXIT_SUCCESS); - } - - void help_topic_valid_triplet(const vcpkg_paths& paths) - { - System::println("Available architecture triplets:"); - auto it = fs::directory_iterator(paths.triplets); - for (; it != fs::directory_iterator(); ++it) - { - System::println(" %s", it->path().stem().filename().string()); - } - } } diff --git a/toolsrc/src/commands_helpers.cpp b/toolsrc/src/commands_helpers.cpp new file mode 100644 index 000000000..0c7ce15bb --- /dev/null +++ b/toolsrc/src/commands_helpers.cpp @@ -0,0 +1,7 @@ +#include "vcpkg_Commands.h" +#include "vcpkg_System.h" + +namespace vcpkg::Commands::Helpers +{ + +} diff --git a/toolsrc/src/commands_import.cpp b/toolsrc/src/commands_import.cpp index 9cfc53d6c..7af2c7185 100644 --- a/toolsrc/src/commands_import.cpp +++ b/toolsrc/src/commands_import.cpp @@ -1,25 +1,97 @@ +#include "pch.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" +#include "Paragraphs.h" +#include "StatusParagraph.h" +#include "vcpkg_Files.h" -namespace vcpkg +namespace vcpkg::Commands::Import { - void import_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + struct Binaries { - static const std::string example = create_example_string(R"(import C:\path\to\CONTROLfile C:\path\to\includedir C:\path\to\projectdir)"); - args.check_exact_arg_count(3, example.c_str()); + std::vector<fs::path> dlls; + std::vector<fs::path> libs; + }; + + static Binaries detect_files_in_directory_ending_with(const fs::path& path) + { + Files::check_is_directory(path); + + Binaries binaries; + + for (auto it = fs::recursive_directory_iterator(path); it != fs::recursive_directory_iterator(); ++it) + { + fs::path file = *it; + // Skip if directory ????? + if (file.extension() == ".dll") + { + binaries.dlls.push_back(file); + } + else if (file.extension() == ".lib") + { + binaries.libs.push_back(file); + } + } + + return binaries; + } + + static void copy_files_into_directory(const std::vector<fs::path>& files, const fs::path& destination_folder) + { + fs::create_directory(destination_folder); + + for (auto const& src_path : files) + { + fs::path dest_path = destination_folder / src_path.filename(); + fs::copy(src_path, dest_path, fs::copy_options::overwrite_existing); + } + } + + static void place_library_files_in(const fs::path& include_directory, const fs::path& project_directory, const fs::path& destination_path) + { + Files::check_is_directory(include_directory); + Files::check_is_directory(project_directory); + Files::check_is_directory(destination_path); + Binaries debug_binaries = detect_files_in_directory_ending_with(project_directory / "Debug"); + Binaries release_binaries = detect_files_in_directory_ending_with(project_directory / "Release"); + + fs::path destination_include_directory = destination_path / "include"; + fs::copy(include_directory, destination_include_directory, fs::copy_options::recursive | fs::copy_options::overwrite_existing); + + copy_files_into_directory(release_binaries.dlls, destination_path / "bin"); + copy_files_into_directory(release_binaries.libs, destination_path / "lib"); + + fs::create_directory(destination_path / "debug"); + copy_files_into_directory(debug_binaries.dlls, destination_path / "debug" / "bin"); + copy_files_into_directory(debug_binaries.libs, destination_path / "debug" / "lib"); + } + + static void do_import(const vcpkg_paths& paths, const fs::path& include_directory, const fs::path& project_directory, const BinaryParagraph& control_file_data) + { + fs::path library_destination_path = paths.package_dir(control_file_data.spec); + fs::create_directory(library_destination_path); + place_library_files_in(include_directory, project_directory, library_destination_path); + + fs::path control_file_path = library_destination_path / "CONTROL"; + std::ofstream(control_file_path) << control_file_data; + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& 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); const fs::path control_file_path(args.command_arguments[0]); const fs::path include_directory(args.command_arguments[1]); const fs::path project_directory(args.command_arguments[2]); - auto pghs = get_paragraphs(control_file_path); + auto pghs = Paragraphs::get_paragraphs(control_file_path); Checks::check_throw(pghs.size() == 1, "Invalid control file for package"); StatusParagraph spgh; spgh.package = BinaryParagraph(pghs[0]); auto& control_file_data = spgh.package; - vcpkg::binary_import(paths, include_directory, project_directory, control_file_data); + do_import(paths, include_directory, project_directory, control_file_data); exit(EXIT_SUCCESS); } } diff --git a/toolsrc/src/commands_install.cpp b/toolsrc/src/commands_install.cpp new file mode 100644 index 000000000..1f5a2234d --- /dev/null +++ b/toolsrc/src/commands_install.cpp @@ -0,0 +1,239 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "vcpkglib.h" +#include "vcpkg_Environment.h" +#include "metrics.h" +#include "vcpkg_Files.h" +#include "vcpkg_System.h" +#include "vcpkg_Dependencies.h" +#include "vcpkg_Input.h" + +namespace vcpkg::Commands::Install +{ + using Dependencies::package_spec_with_install_plan; + using Dependencies::install_plan_type; + + static void install_and_write_listfile(const vcpkg_paths& paths, const BinaryParagraph& bpgh) + { + std::vector<std::string> output; + + const fs::path package_prefix_path = paths.package_dir(bpgh.spec); + const size_t prefix_length = package_prefix_path.native().size(); + + const triplet& target_triplet = bpgh.spec.target_triplet(); + const std::string& target_triplet_as_string = target_triplet.canonical_name(); + std::error_code ec; + fs::create_directory(paths.installed / target_triplet_as_string, ec); + output.push_back(Strings::format(R"(%s)", target_triplet_as_string)); + + for (auto it = fs::recursive_directory_iterator(package_prefix_path); it != fs::recursive_directory_iterator(); ++it) + { + const std::string filename = it->path().filename().generic_string(); + if (fs::is_regular_file(it->status()) && (_stricmp(filename.c_str(), "CONTROL") == 0 || _stricmp(filename.c_str(), "BUILD_INFO") == 0)) + { + // Do not copy the control file + continue; + } + + const std::string suffix = it->path().generic_u8string().substr(prefix_length + 1); + const fs::path target = paths.installed / target_triplet_as_string / suffix; + + auto status = it->status(ec); + if (ec) + { + System::println(System::color::error, "failed: %s: %s", it->path().u8string(), ec.message()); + continue; + } + + 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)", target_triplet_as_string, 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(*it, 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)", target_triplet_as_string, suffix)); + continue; + } + + if (!fs::status_known(status)) + { + System::println(System::color::error, "failed: %s: unknown status", it->path().u8string()); + continue; + } + + System::println(System::color::error, "failed: %s: cannot handle file type", it->path().u8string()); + } + + Files::write_all_lines(paths.listfile_path(bpgh), 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<StatusParagraph_and_associated_files>& pgh_and_files, const triplet& triplet) + { + std::vector<std::string> output; + for (const StatusParagraph_and_associated_files& t : pgh_and_files) + { + if (t.pgh.package.spec.target_triplet() != triplet) + { + continue; + } + + output.insert(output.end(), t.files.cbegin(), t.files.cend()); + } + + std::sort(output.begin(), output.end()); + return output; + } + + static ImmutableSortedVector<std::string> build_list_of_package_files(const fs::path& package_dir) + { + const std::vector<fs::path> package_file_paths = Files::recursive_find_all_files_in_dir(package_dir); + std::vector<std::string> package_files; + const size_t package_remove_char_count = package_dir.generic_string().size() + 1; // +1 for the slash + std::transform(package_file_paths.cbegin(), package_file_paths.cend(), std::back_inserter(package_files), [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 ImmutableSortedVector<std::string>::create(std::move(package_files)); + } + + static ImmutableSortedVector<std::string> build_list_of_installed_files(const std::vector<StatusParagraph_and_associated_files>& 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 ImmutableSortedVector<std::string>::create(std::move(installed_files)); + } + + void install_package(const vcpkg_paths& 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.target_triplet(); + const std::vector<StatusParagraph_and_associated_files> pgh_and_files = get_installed_files(paths, *status_db); + + const ImmutableSortedVector<std::string> package_files = build_list_of_package_files(package_dir); + const ImmutableSortedVector<std::string> installed_files = build_list_of_installed_files(pgh_and_files, triplet); + + std::vector<std::string> intersection; + std::set_intersection(package_files.cbegin(), package_files.cend(), + installed_files.cbegin(), installed_files.cend(), + 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::Joiner::on("\n ").join(intersection)); + System::println(""); + exit(EXIT_FAILURE); + } + + StatusParagraph spgh; + spgh.package = binary_paragraph; + spgh.want = want_t::install; + spgh.state = install_state_t::half_installed; + for (auto&& dep : spgh.package.depends) + { + if (status_db->find_installed(dep, spgh.package.spec.target_triplet()) == status_db->end()) + { + Checks::unreachable(); + } + } + write_update(paths, spgh); + status_db->insert(std::make_unique<StatusParagraph>(spgh)); + + install_and_write_listfile(paths, spgh.package); + + spgh.state = install_state_t::installed; + write_update(paths, spgh); + status_db->insert(std::make_unique<StatusParagraph>(spgh)); + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + { + static const std::string example = Commands::Help::create_example_string("install zlib zlib:x64-windows curl boost"); + args.check_min_arg_count(1, example); + StatusParagraphs status_db = database_load_check(paths); + + std::vector<package_spec> specs = Input::check_and_get_package_specs(args.command_arguments, default_target_triplet, example); + Input::check_triplets(specs, paths); + std::vector<package_spec_with_install_plan> install_plan = Dependencies::create_install_plan(paths, specs, status_db); + Checks::check_exit(!install_plan.empty(), "Install plan cannot be empty"); + + std::string specs_string = install_plan[0].spec.toString(); + for (size_t i = 1; i < install_plan.size(); ++i) + { + specs_string.push_back(','); + specs_string.append(install_plan[i].spec.toString()); + } + TrackProperty("installplan", specs_string); + Environment::ensure_utilities_on_path(paths); + + for (const package_spec_with_install_plan& action : install_plan) + { + try + { + if (action.plan.plan_type == install_plan_type::ALREADY_INSTALLED) + { + if (std::find(specs.begin(), specs.end(), action.spec) != specs.end()) + { + System::println(System::color::success, "Package %s is already installed", action.spec); + } + } + else if (action.plan.plan_type == install_plan_type::BUILD_AND_INSTALL) + { + Commands::Build::build_package(*action.plan.source_pgh, action.spec, paths, paths.port_dir(action.spec)); + const BinaryParagraph bpgh = try_load_cached_package(paths, action.spec).get_or_throw(); + install_package(paths, bpgh, &status_db); + System::println(System::color::success, "Package %s is installed", action.spec); + } + else if (action.plan.plan_type == install_plan_type::INSTALL) + { + install_package(paths, *action.plan.binary_pgh, &status_db); + System::println(System::color::success, "Package %s is installed", action.spec); + } + else + Checks::unreachable(); + } + catch (const std::exception& e) + { + System::println(System::color::error, "Error: Could not install package %s: %s", action.spec, e.what()); + exit(EXIT_FAILURE); + } + } + + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/commands_installation.cpp b/toolsrc/src/commands_installation.cpp deleted file mode 100644 index 7e7da9e3f..000000000 --- a/toolsrc/src/commands_installation.cpp +++ /dev/null @@ -1,176 +0,0 @@ -#include "vcpkg_Commands.h" -#include "vcpkg.h" -#include <fstream> -#include "vcpkg_Environment.h" -#include "metrics.h" -#include "vcpkg_Files.h" -#include "post_build_lint.h" -#include "vcpkg_System.h" -#include "vcpkg_Dependencies.h" -#include "vcpkg_Input.h" - -namespace vcpkg -{ - static void create_binary_control_file(const vcpkg_paths& paths, const fs::path& port_dir, const triplet& target_triplet) - { - auto pghs = get_paragraphs(port_dir / "CONTROL"); - Checks::check_exit(pghs.size() == 1, "Error: invalid control file"); - auto bpgh = BinaryParagraph(SourceParagraph(pghs[0]), target_triplet); - const fs::path binary_control_file = paths.packages / bpgh.dir() / "CONTROL"; - std::ofstream(binary_control_file) << bpgh; - } - - static void build_internal(const package_spec& spec, const vcpkg_paths& paths, const fs::path& port_dir) - { - const fs::path ports_cmake_script_path = paths.ports_cmake; - auto&& target_triplet = spec.target_triplet(); - const std::wstring command = Strings::wformat(LR"("%%VS140COMNTOOLS%%..\..\VC\vcvarsall.bat" %s && cmake -DCMD=BUILD -DPORT=%s -DTARGET_TRIPLET=%s "-DCURRENT_PORT_DIR=%s/." -P "%s")", - Strings::utf8_to_utf16(target_triplet.architecture()), - Strings::utf8_to_utf16(spec.name()), - Strings::utf8_to_utf16(target_triplet.canonical_name()), - port_dir.generic_wstring(), - ports_cmake_script_path.generic_wstring()); - - System::Stopwatch2 timer; - timer.start(); - int return_code = System::cmd_execute(command); - timer.stop(); - TrackMetric("buildtimeus-" + to_string(spec), timer.microseconds()); - - if (return_code != 0) - { - System::println(System::color::error, "Error: building package %s failed", to_string(spec)); - System::println("Please ensure sure 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." - , to_string(spec), version()); - TrackProperty("error", "build failed"); - TrackProperty("build_error", to_string(spec)); - exit(EXIT_FAILURE); - } - - perform_all_checks(spec, paths); - - create_binary_control_file(paths, port_dir, target_triplet); - - // const fs::path port_buildtrees_dir = paths.buildtrees / spec.name; - // delete_directory(port_buildtrees_dir); - } - - static void build_internal(const package_spec& spec, const vcpkg_paths& paths) - { - return build_internal(spec, paths, paths.ports / spec.name()); - } - - void install_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) - { - static const std::string example = create_example_string("install zlib zlib:x64-windows curl boost"); - args.check_min_arg_count(1, example.c_str()); - StatusParagraphs status_db = database_load_check(paths); - - std::vector<package_spec> specs = Input::check_and_get_package_specs(args.command_arguments, default_target_triplet, example.c_str()); - Input::check_triplets(specs, paths); - std::vector<package_spec> install_plan = Dependencies::create_dependency_ordered_install_plan(paths, specs, status_db); - Checks::check_exit(!install_plan.empty(), "Install plan cannot be empty"); - std::string specs_string = to_string(install_plan[0]); - for (size_t i = 1; i < install_plan.size(); ++i) - { - specs_string.push_back(','); - specs_string.append(to_string(install_plan[i])); - } - TrackProperty("installplan", specs_string); - Environment::ensure_utilities_on_path(paths); - - for (const package_spec& spec : install_plan) - { - if (status_db.find_installed(spec.name(), spec.target_triplet()) != status_db.end()) - { - System::println(System::color::success, "Package %s is already installed", spec); - continue; - } - - fs::path package_path = paths.package_dir(spec); - - expected<std::string> file_contents = Files::get_contents(package_path / "CONTROL"); - - try - { - if (file_contents.error_code()) - { - build_internal(spec, paths); - file_contents = Files::get_contents(package_path / "CONTROL"); - if (file_contents.error_code()) - { - file_contents.get_or_throw(); - } - } - - auto pghs = parse_paragraphs(file_contents.get_or_throw()); - Checks::check_throw(pghs.size() == 1, "multiple paragraphs in control file"); - install_package(paths, BinaryParagraph(pghs[0]), status_db); - System::println(System::color::success, "Package %s is installed", spec); - } - catch (const std::exception& e) - { - System::println(System::color::error, "Error: Could not install package %s: %s", spec, e.what()); - exit(EXIT_FAILURE); - } - } - - exit(EXIT_SUCCESS); - } - - void build_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) - { - static const std::string example = create_example_string("build zlib:x64-windows"); - - // Installing multiple packages leads to unintuitive behavior if one of them depends on another. - // Allowing only 1 package for now. - - args.check_exact_arg_count(1, example.c_str()); - StatusParagraphs status_db = database_load_check(paths); - - const package_spec spec = Input::check_and_get_package_spec(args.command_arguments.at(0), default_target_triplet, example.c_str()); - Input::check_triplet(spec.target_triplet(), paths); - std::unordered_set<package_spec> unmet_dependencies = Dependencies::find_unmet_dependencies(paths, spec, status_db); - if (!unmet_dependencies.empty()) - { - 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 package_spec& p : unmet_dependencies) - { - System::println(" %s", to_string(p)); - } - System::println(""); - exit(EXIT_FAILURE); - } - - Environment::ensure_utilities_on_path(paths); - build_internal(spec, paths); - exit(EXIT_SUCCESS); - } - - void build_external_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) - { - static const std::string example = create_example_string(R"(build_external zlib2 C:\path\to\dir\with\controlfile\)"); - args.check_exact_arg_count(2, example.c_str()); - - expected<package_spec> current_spec = package_spec::from_string(args.command_arguments[0], default_target_triplet); - if (auto spec = current_spec.get()) - { - Input::check_triplet(spec->target_triplet(), paths); - Environment::ensure_utilities_on_path(paths); - const fs::path port_dir = args.command_arguments.at(1); - build_internal(*spec, paths, port_dir); - exit(EXIT_SUCCESS); - } - - System::println(System::color::error, "Error: %s: %s", current_spec.error_code().message(), args.command_arguments[0]); - print_example(Strings::format("%s zlib:x64-windows", args.command).c_str()); - exit(EXIT_FAILURE); - } -} diff --git a/toolsrc/src/commands_integration.cpp b/toolsrc/src/commands_integrate.cpp index 6a11d6ec4..03c51b5a7 100644 --- a/toolsrc/src/commands_integration.cpp +++ b/toolsrc/src/commands_integrate.cpp @@ -1,18 +1,11 @@ -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#include <shellapi.h> +#include "pch.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" -#include <fstream> -#include <iostream> -#include <regex> -#include <array> #include "vcpkg_Environment.h" #include "vcpkg_Checks.h" #include "vcpkg_System.h" #include "vcpkg_Files.h" -namespace vcpkg +namespace vcpkg::Commands::Integrate { static const std::array<fs::path, 2> old_system_target_files = { "C:/Program Files (x86)/MSBuild/14.0/Microsoft.Common.Targets/ImportBefore/vcpkg.nuget.targets", @@ -175,7 +168,7 @@ namespace vcpkg bool should_install_system = true; if (fs::exists(system_wide_targets_file)) { - auto system_wide_file_contents = Files::get_contents(system_wide_targets_file); + auto system_wide_file_contents = Files::read_contents(system_wide_targets_file); if (auto contents_data = system_wide_file_contents.get()) { std::regex re(R"###(<!-- version (\d+) -->)###"); @@ -295,11 +288,11 @@ With a project open, go to Tools->NuGet Package Manager->Package Manager Console " vcpkg integrate remove Remove user-wide integration\n" " vcpkg integrate project Generate a referencing nuget package for individual VS project use\n"; - void integrate_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) { static const std::string example = Strings::format("Commands:\n" "%s", INTEGRATE_COMMAND_HELPSTRING); - args.check_exact_arg_count(1, example.c_str()); + args.check_exact_arg_count(1, example); if (args.command_arguments[0] == "install") { diff --git a/toolsrc/src/commands_list.cpp b/toolsrc/src/commands_list.cpp index 194e4b435..34a9c2fc8 100644 --- a/toolsrc/src/commands_list.cpp +++ b/toolsrc/src/commands_list.cpp @@ -1,32 +1,68 @@ +#include "pch.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" +#include "vcpkglib.h" #include "vcpkg_System.h" +#include "vcpkglib_helpers.h" -namespace vcpkg +namespace vcpkg::Commands::List { - void list_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + static void do_print(const StatusParagraph& pgh) { - args.check_exact_arg_count(0); + System::println("%-27s %-16s %s", + pgh.package.displayname(), + pgh.package.version, + details::shorten_description(pgh.package.description)); + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& 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); - std::vector<std::string> packages_output; - for (auto&& pgh : database_load_check(paths)) + const StatusParagraphs status_paragraphs = database_load_check(paths); + std::vector<StatusParagraph> installed_packages; + for (auto&& pgh : status_paragraphs) { if (pgh->state == install_state_t::not_installed && pgh->want == want_t::purge) continue; - packages_output.push_back(Strings::format("%-27s %-16s %s", - pgh->package.displayname(), - pgh->package.version, - shorten_description(pgh->package.description))); + installed_packages.push_back(*pgh); + } + + if (installed_packages.empty()) + { + System::println("No packages are installed. Did you mean `search`?"); + exit(EXIT_SUCCESS); } - std::sort(packages_output.begin(), packages_output.end()); - for (auto&& package : packages_output) + + 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) { - System::println(package.c_str()); + for (const StatusParagraph& status_paragraph : installed_packages) + { + do_print(status_paragraph); + } } - if (packages_output.empty()) + else { - System::println("No packages are installed. Did you mean `search`?"); + // 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); + } } + exit(EXIT_SUCCESS); } } diff --git a/toolsrc/src/commands_other.cpp b/toolsrc/src/commands_other.cpp deleted file mode 100644 index 07549a437..000000000 --- a/toolsrc/src/commands_other.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "vcpkg_Commands.h" -#include "vcpkg_System.h" -#include "vcpkg.h" - -namespace vcpkg -{ - 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 --purge <pkg> Uninstall and delete a package. \n" - " vcpkg list List installed packages\n" - " vcpkg update Display list of packages for updating\n" - "\n" - "%s" // Integration help - "\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_COMMAND_HELPSTRING); - } - - std::string create_example_string(const char* command_and_arguments) - { - std::string cs = Strings::format("Example:\n" - " vcpkg %s", command_and_arguments); - return cs; - } - - void print_example(const char* command_and_arguments) - { - System::println(create_example_string(command_and_arguments).c_str()); - } - - void internal_test_command(const vcpkg_cmd_arguments& /*args*/, const vcpkg_paths& /*paths*/) - { - // auto data = FormatEventData("test"); - // Track(data); - exit(EXIT_SUCCESS); - } - - const std::vector<package_name_and_function<command_type_a>>& get_available_commands_type_a() - { - static std::vector<package_name_and_function<command_type_a>> t = { - {"install", install_command}, - {"remove", remove_command}, - {"build", build_command}, - {"build_external", build_external_command} - }; - return t; - } - - const std::vector<package_name_and_function<command_type_b>>& get_available_commands_type_b() - { - static std::vector<package_name_and_function<command_type_b>> t = { - {"help", help_command}, - {"search", search_command}, - {"list", list_command}, - {"integrate", integrate_command}, - {"owns", owns_command}, - {"update", update_command}, - {"edit", edit_command}, - {"create", create_command}, - {"import", import_command}, - {"cache", cache_command}, - {"internal_test", internal_test_command}, - }; - return t; - } - - const std::vector<package_name_and_function<command_type_c>>& get_available_commands_type_c() - { - static std::vector<package_name_and_function<command_type_c>> t = { - {"version", &version_command}, - {"contact", &contact_command} - }; - return t; - } -} diff --git a/toolsrc/src/commands_owns.cpp b/toolsrc/src/commands_owns.cpp index b3dab2e44..6c4c20c44 100644 --- a/toolsrc/src/commands_owns.cpp +++ b/toolsrc/src/commands_owns.cpp @@ -1,13 +1,31 @@ +#include "pch.h" #include "vcpkg_Commands.h" #include "vcpkg_System.h" -#include "vcpkg.h" +#include "vcpkglib.h" -namespace vcpkg +namespace vcpkg::Commands::Owns { - void owns_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + static void search_file(const vcpkg_paths& paths, const std::string& file_substr, const StatusParagraphs& status_db) { - static const std::string example = Strings::format("The argument should be a pattern to search for. %s", create_example_string("owns zlib.dll")); - args.check_exact_arg_count(1, example.c_str()); + const std::vector<StatusParagraph_and_associated_files> installed_files = get_installed_files(paths, status_db); + for (const StatusParagraph_and_associated_files& pgh_and_file : installed_files) + { + const StatusParagraph& pgh = pgh_and_file.pgh; + + for (const std::string& file : pgh_and_file.files) + { + if (file.find(file_substr) != std::string::npos) + { + System::println("%s: %s", pgh.package.displayname(), file); + } + } + } + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& 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); StatusParagraphs status_db = database_load_check(paths); search_file(paths, args.command_arguments[0], status_db); diff --git a/toolsrc/src/commands_portsdiff.cpp b/toolsrc/src/commands_portsdiff.cpp new file mode 100644 index 000000000..e75633b3c --- /dev/null +++ b/toolsrc/src/commands_portsdiff.cpp @@ -0,0 +1,167 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "vcpkg_System.h" +#include "vcpkg_Maps.h" +#include "Paragraphs.h" +#include "SourceParagraph.h" +#include "vcpkg_Environment.h" + +namespace vcpkg::Commands::PortsDiff +{ + static void do_print_name_and_version(const std::vector<std::string>& ports_to_print, const std::map<std::string, std::string>& names_and_versions) + { + for (const std::string& name : ports_to_print) + { + const std::string& version = names_and_versions.at(name); + std::cout << std::left + << std::setw(20) << name << ' ' + << std::setw(16) << version << ' ' + << '\n'; + } + } + + static void do_print_name_and_previous_version_and_current_version(const std::vector<std::string>& ports_to_print, + const std::map<std::string, std::string>& previous_names_and_versions, + const std::map<std::string, std::string>& current_names_and_versions) + { + for (const std::string& name : ports_to_print) + { + if (name == "") + { + continue; + } + + const std::string& previous_version = previous_names_and_versions.at(name); + const std::string& current_version = current_names_and_versions.at(name); + std::cout << std::left + << std::setw(20) << name << ' ' + << std::setw(16) << previous_version << " -> " << current_version + << '\n'; + } + } + + static std::map<std::string, std::string> read_all_ports(const fs::path& ports_folder_path) + { + std::map<std::string, std::string> names_and_versions; + + for (auto it = fs::directory_iterator(ports_folder_path); it != fs::directory_iterator(); ++it) + { + const fs::path& path = it->path(); + + try + { + auto pghs = Paragraphs::get_paragraphs(path / "CONTROL"); + if (pghs.empty()) + continue; + auto srcpgh = SourceParagraph(pghs[0]); + names_and_versions.emplace(srcpgh.name, srcpgh.version); + } + catch (std::runtime_error const&) + { + } + } + + return names_and_versions; + } + + static std::map<std::string, std::string> read_ports_from_commit(const vcpkg_paths& paths, const std::wstring& git_commit_id) + { + const fs::path dot_git_dir = paths.root / ".git"; + const std::wstring ports_dir_name_as_string = paths.ports.filename().native(); + const fs::path temp_checkout_path = paths.root / Strings::wformat(L"%s-%s", ports_dir_name_as_string, git_commit_id); + fs::create_directory(temp_checkout_path); + 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"(git --git-dir="%s" --work-tree="%s" checkout %s -f -q -- %s %s & git reset >NUL)", + dot_git_dir.native(), + temp_checkout_path.native(), + git_commit_id, + checkout_this_dir, + L".vcpkg-root"); + System::cmd_execute(cmd); + std::map<std::string, std::string> names_and_versions = read_all_ports(temp_checkout_path / ports_dir_name_as_string); + fs::remove_all(temp_checkout_path); + return names_and_versions; + } + + static void check_commit_exists(const std::wstring& git_commit_id) + { + static const std::string VALID_COMMIT_OUTPUT = "commit\n"; + + const std::wstring cmd = Strings::wformat(LR"(git cat-file -t %s 2>NUL)", git_commit_id); + const System::exit_code_and_output output = System::cmd_execute_and_capture_output(cmd); + Checks::check_exit(output.output == VALID_COMMIT_OUTPUT, "Invalid commit id %s", Strings::utf16_to_utf8(git_commit_id)); + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& 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); + + Environment::ensure_git_on_path(paths); + const std::wstring git_commit_id_for_previous_snapshot = Strings::utf8_to_utf16(args.command_arguments.at(0)); + const std::wstring git_commit_id_for_current_snapshot = args.command_arguments.size() < 2 ? L"HEAD" : Strings::utf8_to_utf16(args.command_arguments.at(1)); + + check_commit_exists(git_commit_id_for_current_snapshot); + check_commit_exists(git_commit_id_for_previous_snapshot); + + const std::map<std::string, std::string> current_names_and_versions = read_ports_from_commit(paths, git_commit_id_for_current_snapshot); + const std::map<std::string, std::string> previous_names_and_versions = 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); + + std::vector<std::string> added_ports; + std::set_difference( + current_ports.cbegin(), current_ports.cend(), + previous_ports.cbegin(), previous_ports.cend(), + std::back_inserter(added_ports)); + + if (!added_ports.empty()) + { + System::println("\nThe following %d ports were added:\n", added_ports.size()); + do_print_name_and_version(added_ports, current_names_and_versions); + } + + std::vector<std::string> removed_ports; + std::set_difference( + previous_ports.cbegin(), previous_ports.cend(), + current_ports.cbegin(), current_ports.cend(), + std::back_inserter(removed_ports)); + + if (!removed_ports.empty()) + { + System::println("\nThe following %d ports were removed:\n", removed_ports.size()); + do_print_name_and_version(removed_ports, previous_names_and_versions); + } + + std::vector<std::string> potentially_updated_ports; + std::set_intersection( + current_ports.cbegin(), current_ports.cend(), + previous_ports.cbegin(), previous_ports.cend(), + std::back_inserter(potentially_updated_ports)); + + std::vector<std::string> updated_ports; + std::copy_if(potentially_updated_ports.cbegin(), potentially_updated_ports.cend(), std::back_inserter(updated_ports), + [&](const std::string& port) -> bool + { + return current_names_and_versions.at(port) != previous_names_and_versions.at(port); + } + ); + + if (!updated_ports.empty()) + { + System::println("\nThe following %d ports were updated:\n", updated_ports.size()); + do_print_name_and_previous_version_and_current_version(updated_ports, previous_names_and_versions, current_names_and_versions); + } + + if (added_ports.empty() && removed_ports.empty() && updated_ports.empty()) + { + System::println("There were no changes in the ports between the two commits."); + } + + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/commands_remove.cpp b/toolsrc/src/commands_remove.cpp index 5bb9ecc96..f49104d1e 100644 --- a/toolsrc/src/commands_remove.cpp +++ b/toolsrc/src/commands_remove.cpp @@ -1,11 +1,18 @@ +#include "pch.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" +#include "vcpkglib.h" #include "vcpkg_System.h" #include "vcpkg_Input.h" +#include "vcpkg_Dependencies.h" -namespace vcpkg +namespace vcpkg::Commands::Remove { + using Dependencies::package_spec_with_remove_plan; + using Dependencies::remove_plan_type; + using Dependencies::request_type; + static const std::string OPTION_PURGE = "--purge"; + static const std::string OPTION_RECURSE = "--recurse"; static void delete_directory(const fs::path& directory) { @@ -21,28 +28,199 @@ namespace vcpkg } } - void remove_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) + static void remove_package(const vcpkg_paths& paths, const package_spec& spec, StatusParagraphs* status_db) + { + StatusParagraph& pkg = **status_db->find(spec.name(), spec.target_triplet()); + + pkg.want = want_t::purge; + pkg.state = install_state_t::half_installed; + write_update(paths, pkg); + + std::fstream listfile(paths.listfile_path(pkg.package), std::ios_base::in | std::ios_base::binary); + if (listfile) + { + std::vector<fs::path> dirs_touched; + std::string suffix; + while (std::getline(listfile, suffix)) + { + if (!suffix.empty() && suffix.back() == '\r') + suffix.pop_back(); + + std::error_code ec; + + auto target = paths.installed / suffix; + + auto status = fs::status(target, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + continue; + } + + if (fs::is_directory(status)) + { + dirs_touched.push_back(target); + } + else if (fs::is_regular_file(status)) + { + fs::remove(target, ec); + if (ec) + { + System::println(System::color::error, "failed: %s: %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::directory_iterator(*b) == fs::directory_iterator()) + { + std::error_code ec; + fs::remove(*b, ec); + if (ec) + { + System::println(System::color::error, "failed: %s", ec.message()); + } + } + } + + listfile.close(); + fs::remove(paths.listfile_path(pkg.package)); + } + + pkg.state = install_state_t::not_installed; + write_update(paths, pkg); + } + + static void sort_packages_by_name(std::vector<const package_spec_with_remove_plan*>* packages) + { + std::sort(packages->begin(), packages->end(), [](const package_spec_with_remove_plan* left, const package_spec_with_remove_plan* right) -> bool + { + return left->spec.name() < right->spec.name(); + }); + } + + static void print_plan(const std::vector<package_spec_with_remove_plan>& plan) + { + std::vector<const package_spec_with_remove_plan*> not_installed; + std::vector<const package_spec_with_remove_plan*> remove; + + for (const package_spec_with_remove_plan& i : plan) + { + if (i.plan.plan_type == remove_plan_type::NOT_INSTALLED) + { + not_installed.push_back(&i); + continue; + } + + if (i.plan.plan_type == remove_plan_type::REMOVE) + { + remove.push_back(&i); + continue; + } + + Checks::unreachable(); + } + + if (!not_installed.empty()) + { + sort_packages_by_name(¬_installed); + System::println("The following packages are not installed, so not removed:\n%s", + Strings::Joiner::on("\n ").prefix(" ").join(not_installed, [](const package_spec_with_remove_plan* p) + { + return p->spec.toString(); + })); + } + + if (!remove.empty()) + { + sort_packages_by_name(&remove); + System::println("The following packages will be removed:\n%s", + Strings::Joiner::on("\n").join(remove, [](const package_spec_with_remove_plan* p) + { + if (p->plan.request_type == Dependencies::request_type::AUTO_SELECTED) + { + return " * " + p->spec.toString(); + } + + if (p->plan.request_type == Dependencies::request_type::USER_REQUESTED) + { + return " " + p->spec.toString(); + } + + Checks::unreachable(); + })); + } + } + + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths, const triplet& default_target_triplet) { - static const std::string example = create_example_string("remove zlib zlib:x64-windows curl boost"); - args.check_min_arg_count(1, example.c_str()); + static const std::string example = Commands::Help::create_example_string("remove zlib zlib:x64-windows curl boost"); + args.check_min_arg_count(1, example); - const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments({OPTION_PURGE}); + const std::unordered_set<std::string> options = args.check_and_get_optional_command_arguments({OPTION_PURGE, OPTION_RECURSE}); auto status_db = database_load_check(paths); - std::vector<package_spec> specs = Input::check_and_get_package_specs(args.command_arguments, default_target_triplet, example.c_str()); + std::vector<package_spec> specs = Input::check_and_get_package_specs(args.command_arguments, default_target_triplet, example); Input::check_triplets(specs, paths); - bool alsoRemoveFolderFromPackages = options.find(OPTION_PURGE) != options.end(); + const bool alsoRemoveFolderFromPackages = options.find(OPTION_PURGE) != options.end(); + const bool isRecursive = options.find(OPTION_RECURSE) != options.end(); + + const std::vector<package_spec_with_remove_plan> remove_plan = Dependencies::create_remove_plan(paths, specs, status_db); + Checks::check_exit(!remove_plan.empty(), "Remove plan cannot be empty"); - for (const package_spec& spec : specs) + print_plan(remove_plan); + + const bool has_non_user_requested_packages = std::find_if(remove_plan.cbegin(), remove_plan.cend(), [](const package_spec_with_remove_plan& package)-> bool + { + return package.plan.request_type != request_type::USER_REQUESTED; + }) != remove_plan.cend(); + + if (has_non_user_requested_packages && !isRecursive) { - deinstall_package(paths, spec, status_db); + System::println(System::color::warning, + "Additional packages (*) need to be removed to complete this operation.\n" + "If you are sure you want to remove them, run the command with the --recurse option"); + exit(EXIT_FAILURE); + } + + for (const package_spec_with_remove_plan& action : remove_plan) + { + const std::string display_name = action.spec.display_name(); + + switch (action.plan.plan_type) + { + case remove_plan_type::NOT_INSTALLED: + System::println(System::color::success, "Package %s is not installed", display_name); + break; + case remove_plan_type::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 remove_plan_type::UNKNOWN: + default: + Checks::unreachable(); + } if (alsoRemoveFolderFromPackages) { - const fs::path spec_package_dir = paths.packages / spec.dir(); - delete_directory(spec_package_dir); + System::println("Purging package %s... ", display_name); + delete_directory(paths.packages / action.spec.dir()); + System::println(System::color::success, "Purging package %s... done", display_name); } } + exit(EXIT_SUCCESS); } } diff --git a/toolsrc/src/commands_search.cpp b/toolsrc/src/commands_search.cpp index c90538e86..3a3226e4b 100644 --- a/toolsrc/src/commands_search.cpp +++ b/toolsrc/src/commands_search.cpp @@ -1,60 +1,74 @@ +#include "pch.h" #include "vcpkg_Commands.h" #include "vcpkg_System.h" -#include "vcpkg.h" -#include <iostream> -#include <iomanip> +#include "Paragraphs.h" +#include "vcpkglib_helpers.h" +#include "SourceParagraph.h" -namespace fs = std::tr2::sys; - -namespace vcpkg +namespace vcpkg::Commands::Search { - template <class Pred> - static void do_print(const vcpkg_paths& paths, Pred predicate) + static std::vector<SourceParagraph> read_all_source_paragraphs(const vcpkg_paths& paths) { + std::vector<SourceParagraph> output; for (auto it = fs::directory_iterator(paths.ports); it != fs::directory_iterator(); ++it) { const fs::path& path = it->path(); try { - auto pghs = get_paragraphs(path / "CONTROL"); + auto pghs = Paragraphs::get_paragraphs(path / "CONTROL"); if (pghs.empty()) - continue; - auto srcpgh = SourceParagraph(pghs[0]); - - if (predicate(srcpgh.name)) { - std::cout << std::left - << std::setw(20) << srcpgh.name << ' ' - << std::setw(16) << srcpgh.version << ' ' - << shorten_description(srcpgh.description) << '\n'; + continue; } + + auto srcpgh = SourceParagraph(pghs[0]); + output.push_back(srcpgh); } catch (std::runtime_error const&) { } } + + return output; + } + + 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 search_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& 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", create_example_string("search png")); - args.check_max_arg_count(1, example.c_str()); + 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::vector<SourceParagraph> source_paragraphs = read_all_source_paragraphs(paths); if (args.command_arguments.size() == 0) { - do_print(paths, [](std::string&) -> bool - { - return true; - }); - exit(EXIT_SUCCESS); + 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; + } - // At this point there is 1 argument - do_print(paths, [&](std::string& port_name) -> bool - { - return Strings::case_insensitive_ascii_find(port_name, args.command_arguments[0]) != port_name.end(); - }); + do_print(source_paragraph); + } + } System::println("\nIf your library is not listed, please open an issue at:\n" " https://github.com/Microsoft/vcpkg/issues"); diff --git a/toolsrc/src/commands_update.cpp b/toolsrc/src/commands_update.cpp index 5d531ef39..a42ae5341 100644 --- a/toolsrc/src/commands_update.cpp +++ b/toolsrc/src/commands_update.cpp @@ -1,11 +1,14 @@ +#include "pch.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" +#include "vcpkglib.h" #include "vcpkg_System.h" #include "vcpkg_Files.h" +#include "Paragraphs.h" +#include "vcpkg_info.h" -namespace vcpkg +namespace vcpkg::Commands::Update { - void update_command(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) + void perform_and_exit(const vcpkg_cmd_arguments& args, const vcpkg_paths& paths) { args.check_exact_arg_count(0); System::println("Using local portfile versions. To update the local portfiles, use `git pull`."); @@ -21,7 +24,7 @@ namespace vcpkg const auto& path = begin_it->path(); try { - auto pghs = get_paragraphs(path / "CONTROL"); + auto pghs = Paragraphs::get_paragraphs(path / "CONTROL"); if (pghs.empty()) continue; auto srcpgh = SourceParagraph(pghs[0]); @@ -35,7 +38,7 @@ namespace vcpkg std::string packages_list; std::vector<std::string> packages_output; - for (auto&& pgh : database_load_check(paths)) + for (auto&& pgh : status_db) { if (pgh->state == install_state_t::not_installed && pgh->want == want_t::purge) continue; @@ -69,20 +72,20 @@ namespace vcpkg System::println("\nTo update these packages, run\n vcpkg remove --purge <pkgs>...\n vcpkg install <pkgs>..."); } - auto version_file = Files::get_contents(paths.root / "toolsrc" / "VERSION.txt"); + auto version_file = Files::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().c_str(), "%d.%d.%d-", &maj2, &min2, &rev2); + auto num2 = sscanf_s(Info::version().c_str(), "%d.%d.%d-", &maj2, &min2, &rev2); if (num1 == 3 && num2 == 3) { if (maj1 != maj2 || min1 != min2 || rev1 != rev2) { - System::println("Different source is available for vcpkg (%d.%d.%d -> %d.%d.%d). Use scripts\\bootstrap.ps1 to update.", + System::println("Different source is available for vcpkg (%d.%d.%d -> %d.%d.%d). Use powershell -exec bypass scripts/bootstrap.ps1 to update.", maj2, min2, rev2, maj1, min1, rev1); } diff --git a/toolsrc/src/commands_version.cpp b/toolsrc/src/commands_version.cpp new file mode 100644 index 000000000..a521b2567 --- /dev/null +++ b/toolsrc/src/commands_version.cpp @@ -0,0 +1,17 @@ +#include "pch.h" +#include "vcpkg_Commands.h" +#include "vcpkg_System.h" +#include "vcpkg_info.h" + +namespace vcpkg::Commands::Version +{ + void perform_and_exit(const vcpkg_cmd_arguments& args) + { + args.check_exact_arg_count(0); + System::println("Vcpkg package management program version %s\n" + "\n" + "See LICENSE.txt for license information.", Info::version() + ); + exit(EXIT_SUCCESS); + } +} diff --git a/toolsrc/src/lib.cpp b/toolsrc/src/lib.cpp deleted file mode 100644 index 45b73ee07..000000000 --- a/toolsrc/src/lib.cpp +++ /dev/null @@ -1,511 +0,0 @@ -#include "vcpkg.h" -#include <iostream> -#include <iomanip> -#include <fstream> -#include <functional> -#include <string> -#include <unordered_map> -#include <memory> -#include <filesystem> -#include <vector> -#include <cassert> -#include "vcpkg_Files.h" -#include "vcpkg_System.h" - -using namespace vcpkg; - -bool vcpkg::g_do_dry_run = false; - -namespace -{ - template <class M, class K, class V> - auto find_or_default(const M& map, const K& key, const V& val) - { - auto it = map.find(key); - if (it == map.end()) - return decltype(it->second)(val); - else - return it->second; - } -} - -namespace -{ - std::fstream open_status_file(const vcpkg_paths& paths, std::ios_base::openmode mode = std::ios_base::app | std::ios_base::in | std::ios_base::out | std::ios_base::binary) - { - return std::fstream(paths.vcpkg_dir_status_file, mode); - } -} - -static StatusParagraphs load_current_database(const fs::path& vcpkg_dir_status_file, const fs::path& vcpkg_dir_status_file_old) -{ - if (!fs::exists(vcpkg_dir_status_file)) - { - if (!fs::exists(vcpkg_dir_status_file_old)) - { - // no status file, use empty db - return StatusParagraphs(); - } - - fs::rename(vcpkg_dir_status_file_old, vcpkg_dir_status_file); - } - - auto text = Files::get_contents(vcpkg_dir_status_file).get_or_throw(); - auto pghs = parse_paragraphs(text); - - std::vector<std::unique_ptr<StatusParagraph>> status_pghs; - for (auto&& p : pghs) - { - status_pghs.push_back(std::make_unique<StatusParagraph>(p)); - } - - return StatusParagraphs(std::move(status_pghs)); -} - -StatusParagraphs vcpkg::database_load_check(const vcpkg_paths& paths) -{ - auto updates_dir = paths.vcpkg_dir_updates; - - std::error_code ec; - fs::create_directory(paths.installed, ec); - fs::create_directory(paths.vcpkg_dir, ec); - fs::create_directory(paths.vcpkg_dir_info, ec); - fs::create_directory(updates_dir, ec); - - const fs::path& status_file = paths.vcpkg_dir_status_file; - const fs::path status_file_old = status_file.parent_path() / "status-old"; - const fs::path status_file_new = status_file.parent_path() / "status-new"; - - StatusParagraphs current_status_db = load_current_database(status_file, status_file_old); - - auto b = fs::directory_iterator(updates_dir); - auto e = fs::directory_iterator(); - if (b == e) - { - // updates directory is empty, control file is up-to-date. - return current_status_db; - } - - for (; b != e; ++b) - { - if (!fs::is_regular_file(b->status())) - continue; - if (b->path().filename() == "incomplete") - continue; - - auto text = Files::get_contents(b->path()).get_or_throw(); - auto pghs = parse_paragraphs(text); - for (auto&& p : pghs) - { - current_status_db.insert(std::make_unique<StatusParagraph>(p)); - } - } - - std::fstream(status_file_new, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc) << current_status_db; - - if (fs::exists(status_file_old)) - fs::remove(status_file_old); - if (fs::exists(status_file)) - fs::rename(status_file, status_file_old); - fs::rename(status_file_new, status_file); - fs::remove(status_file_old); - - b = fs::directory_iterator(updates_dir); - for (; b != e; ++b) - { - if (!fs::is_regular_file(b->status())) - continue; - fs::remove(b->path()); - } - - return current_status_db; -} - -static fs::path listfile_path(const vcpkg_paths& paths, const BinaryParagraph& pgh) -{ - return paths.vcpkg_dir_info / (pgh.fullstem() + ".list"); -} - -static std::string get_fullpkgname_from_listfile(const fs::path& path) -{ - auto ret = path.stem().generic_u8string(); - std::replace(ret.begin(), ret.end(), '_', ':'); - return ret; -} - -static void write_update(const vcpkg_paths& paths, const StatusParagraph& p) -{ - static int update_id = 0; - auto my_update_id = update_id++; - auto tmp_update_filename = paths.vcpkg_dir_updates / "incomplete"; - auto update_filename = paths.vcpkg_dir_updates / std::to_string(my_update_id); - std::fstream fs(tmp_update_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); - fs << p; - fs.close(); - fs::rename(tmp_update_filename, update_filename); -} - -static void install_and_write_listfile(const vcpkg_paths& paths, const BinaryParagraph& bpgh) -{ - std::fstream listfile(listfile_path(paths, bpgh), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); - - auto package_prefix_path = paths.package_dir(bpgh.spec); - auto prefix_length = package_prefix_path.native().size(); - - const triplet& target_triplet = bpgh.spec.target_triplet(); - const std::string& target_triplet_as_string = target_triplet.canonical_name(); - std::error_code ec; - fs::create_directory(paths.installed / target_triplet_as_string, ec); - listfile << target_triplet << "\n"; - - for (auto it = fs::recursive_directory_iterator(package_prefix_path); it != fs::recursive_directory_iterator(); ++it) - { - const auto& filename = it->path().filename(); - if (fs::is_regular_file(it->status()) && (filename == "CONTROL" || filename == "control")) - { - // Do not copy the control file - continue; - } - - auto suffix = it->path().generic_u8string().substr(prefix_length + 1); - auto target = paths.installed / target_triplet_as_string / suffix; - - auto status = it->status(ec); - if (ec) - { - System::println(System::color::error, "failed: %s: %s", it->path().u8string(), ec.message()); - continue; - } - if (fs::is_directory(status)) - { - fs::create_directory(target, ec); - if (ec) - { - System::println(System::color::error, "failed: %s: %s", target.u8string(), ec.message()); - } - - listfile << target_triplet << "/" << suffix << "\n"; - } - else if (fs::is_regular_file(status)) - { - fs::copy_file(*it, target, ec); - if (ec) - { - System::println(System::color::error, "failed: %s: %s", target.u8string(), ec.message()); - } - listfile << target_triplet << "/" << suffix << "\n"; - } - else if (!fs::status_known(status)) - { - System::println(System::color::error, "failed: %s: unknown status", it->path().u8string()); - } - else - System::println(System::color::error, "failed: %s: cannot handle file type", it->path().u8string()); - } - - listfile.close(); -} - -// TODO: Refactoring between this function and install_package -std::vector<std::string> vcpkg::get_unmet_package_dependencies(const vcpkg_paths& paths, const package_spec& spec, const StatusParagraphs& status_db) -{ - std::vector<std::unordered_map<std::string, std::string>> pghs; - { - const fs::path packages_dir_control_file_path = paths.package_dir(spec) / "CONTROL"; - - auto control_contents_maybe = Files::get_contents(packages_dir_control_file_path); - if (auto control_contents = control_contents_maybe.get()) - { - try - { - pghs = parse_paragraphs(*control_contents); - } - catch (std::runtime_error) - { - } - Checks::check_exit(pghs.size() == 1, "Invalid control file at %s", packages_dir_control_file_path.string()); - return BinaryParagraph(pghs[0]).depends; - } - } - - const fs::path ports_dir_control_file_path = paths.port_dir(spec) / "CONTROL"; - auto control_contents_maybe = Files::get_contents(ports_dir_control_file_path); - if (auto control_contents = control_contents_maybe.get()) - { - try - { - pghs = parse_paragraphs(*control_contents); - } - catch (std::runtime_error) - { - } - Checks::check_exit(pghs.size() == 1, "Invalid control file at %s", ports_dir_control_file_path.string()); - return SourceParagraph(pghs[0]).depends; - } - - Checks::exit_with_message("Could not find package named %s", spec); -} - -void vcpkg::install_package(const vcpkg_paths& paths, const BinaryParagraph& binary_paragraph, StatusParagraphs& status_db) -{ - StatusParagraph spgh; - spgh.package = binary_paragraph; - spgh.want = want_t::install; - spgh.state = install_state_t::half_installed; - for (const std::string& dependency : spgh.package.depends) - { - if (status_db.find_installed(dependency, spgh.package.spec.target_triplet()) == status_db.end()) - { - std::abort(); - } - } - write_update(paths, spgh); - status_db.insert(std::make_unique<StatusParagraph>(spgh)); - - install_and_write_listfile(paths, spgh.package); - - spgh.state = install_state_t::installed; - write_update(paths, spgh); - status_db.insert(std::make_unique<StatusParagraph>(spgh)); -} - -enum class deinstall_plan -{ - not_installed, - dependencies_not_satisfied, - should_deinstall -}; - -static deinstall_plan deinstall_package_plan( - const StatusParagraphs::iterator package_it, - const StatusParagraphs& status_db, - std::vector<const StatusParagraph*>& dependencies_out) -{ - dependencies_out.clear(); - - if (package_it == status_db.end() || (*package_it)->state == install_state_t::not_installed) - { - return deinstall_plan::not_installed; - } - - auto& pkg = (*package_it)->package; - - for (auto&& inst_pkg : status_db) - { - if (inst_pkg->want != want_t::install) - continue; - if (inst_pkg->package.spec.target_triplet() != pkg.spec.target_triplet()) - continue; - - const auto& deps = inst_pkg->package.depends; - - if (std::find(deps.begin(), deps.end(), pkg.spec.name()) != deps.end()) - { - dependencies_out.push_back(inst_pkg.get()); - } - } - - if (!dependencies_out.empty()) - return deinstall_plan::dependencies_not_satisfied; - - return deinstall_plan::should_deinstall; -} - -void vcpkg::deinstall_package(const vcpkg_paths& paths, const package_spec& spec, StatusParagraphs& status_db) -{ - auto package_it = status_db.find(spec.name(), spec.target_triplet()); - if (package_it == status_db.end()) - { - System::println(System::color::success, "Package %s is not installed", spec); - return; - } - - auto& pkg = **package_it; - - std::vector<const StatusParagraph*> deps; - auto plan = deinstall_package_plan(package_it, status_db, deps); - switch (plan) - { - case deinstall_plan::not_installed: - System::println(System::color::success, "Package %s is not installed", spec); - return; - case deinstall_plan::dependencies_not_satisfied: - System::println(System::color::error, "Error: Cannot remove package %s:", spec); - for (auto&& dep : deps) - { - System::println(" %s depends on %s", dep->package.displayname(), pkg.package.displayname()); - } - exit(EXIT_FAILURE); - case deinstall_plan::should_deinstall: - break; - default: - Checks::unreachable(); - } - - pkg.want = want_t::purge; - pkg.state = install_state_t::half_installed; - write_update(paths, pkg); - - std::fstream listfile(listfile_path(paths, pkg.package), std::ios_base::in | std::ios_base::binary); - if (listfile) - { - std::vector<fs::path> dirs_touched; - std::string suffix; - while (std::getline(listfile, suffix)) - { - if (!suffix.empty() && suffix.back() == '\r') - suffix.pop_back(); - - std::error_code ec; - - auto target = paths.installed / suffix; - - auto status = fs::status(target, ec); - if (ec) - { - System::println(System::color::error, "failed: %s", ec.message()); - continue; - } - - if (fs::is_directory(status)) - { - dirs_touched.push_back(target); - } - else if (fs::is_regular_file(status)) - { - fs::remove(target, ec); - if (ec) - { - System::println(System::color::error, "failed: %s: %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::directory_iterator(*b) == fs::directory_iterator()) - { - std::error_code ec; - fs::remove(*b, ec); - if (ec) - { - System::println(System::color::error, "failed: %s", ec.message()); - } - } - } - - listfile.close(); - fs::remove(listfile_path(paths, pkg.package)); - } - - pkg.state = install_state_t::not_installed; - write_update(paths, pkg); - System::println(System::color::success, "Package %s was successfully removed", pkg.package.displayname()); -} - -void vcpkg::search_file(const vcpkg_paths& paths, const std::string& file_substr, const StatusParagraphs& status_db) -{ - std::string line; - - for (auto&& pgh : status_db) - { - if (pgh->state != install_state_t::installed) - continue; - - std::fstream listfile(listfile_path(paths, pgh->package)); - while (std::getline(listfile, line)) - { - if (line.empty()) - { - continue; - } - - if (line.find(file_substr) != std::string::npos) - { - System::println("%s: %s", pgh->package.displayname(), line); - } - } - } -} - -namespace -{ - struct Binaries - { - std::vector<fs::path> dlls; - std::vector<fs::path> libs; - }; - - Binaries detect_files_in_directory_ending_with(const fs::path& path) - { - Files::check_is_directory(path); - - Binaries binaries; - - for (auto it = fs::recursive_directory_iterator(path); it != fs::recursive_directory_iterator(); ++it) - { - fs::path file = *it; - // Skip if directory ????? - if (file.extension() == ".dll") - { - binaries.dlls.push_back(file); - } - else if (file.extension() == ".lib") - { - binaries.libs.push_back(file); - } - } - - return binaries; - } - - void copy_files_into_directory(const std::vector<fs::path>& files, const fs::path& destination_folder) - { - fs::create_directory(destination_folder); - - for (auto const& src_path : files) - { - fs::path dest_path = destination_folder / src_path.filename(); - fs::copy(src_path, dest_path, fs::copy_options::overwrite_existing); - } - } - - void place_library_files_in(const fs::path& include_directory, const fs::path& project_directory, const fs::path& destination_path) - { - Files::check_is_directory(include_directory); - Files::check_is_directory(project_directory); - Files::check_is_directory(destination_path); - Binaries debug_binaries = detect_files_in_directory_ending_with(project_directory / "Debug"); - Binaries release_binaries = detect_files_in_directory_ending_with(project_directory / "Release"); - - fs::path destination_include_directory = destination_path / "include"; - fs::copy(include_directory, destination_include_directory, fs::copy_options::recursive | fs::copy_options::overwrite_existing); - - copy_files_into_directory(release_binaries.dlls, destination_path / "bin"); - copy_files_into_directory(release_binaries.libs, destination_path / "lib"); - - fs::create_directory(destination_path / "debug"); - copy_files_into_directory(debug_binaries.dlls, destination_path / "debug" / "bin"); - copy_files_into_directory(debug_binaries.libs, destination_path / "debug" / "lib"); - } -} - -void vcpkg::binary_import(const vcpkg_paths& paths, const fs::path& include_directory, const fs::path& project_directory, const BinaryParagraph& control_file_data) -{ - fs::path library_destination_path = paths.package_dir(control_file_data.spec); - fs::create_directory(library_destination_path); - place_library_files_in(include_directory, project_directory, library_destination_path); - - fs::path control_file_path = library_destination_path / "CONTROL"; - std::ofstream(control_file_path) << control_file_data; -} diff --git a/toolsrc/src/main.cpp b/toolsrc/src/main.cpp deleted file mode 100644 index 2200cd105..000000000 --- a/toolsrc/src/main.cpp +++ /dev/null @@ -1,246 +0,0 @@ -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> - -#include <iostream> -#include <fstream> -#include <memory> -#include <cassert> -#include "vcpkg.h" -#include "vcpkg_Commands.h" -#include "metrics.h" -#include <Shlobj.h> -#include "vcpkg_Files.h" -#include "vcpkg_System.h" -#include "vcpkg_Input.h" - -using namespace vcpkg; - -bool g_debugging = false; - -void invalid_command(const std::string& cmd) -{ - System::println(System::color::error, "invalid command: %s", cmd); - print_usage(); - exit(EXIT_FAILURE); -} - -static void inner(const vcpkg_cmd_arguments& args) -{ - TrackProperty("command", args.command); - if (args.command.empty()) - { - print_usage(); - exit(EXIT_FAILURE); - } - - if (auto command_function = find_command(args.command, get_available_commands_type_c())) - { - return command_function(args); - } - - fs::path vcpkg_root_dir; - if (args.vcpkg_root_dir != nullptr) - { - vcpkg_root_dir = fs::absolute(Strings::utf8_to_utf16(*args.vcpkg_root_dir)); - } - else - { - auto vcpkg_root_dir_env = System::wdupenv_str(L"VCPKG_ROOT"); - - if (!vcpkg_root_dir_env.empty()) - { - vcpkg_root_dir = fs::absolute(vcpkg_root_dir_env); - } - else - { - vcpkg_root_dir = Files::find_file_recursively_up(fs::absolute(System::get_exe_path_of_current_process()), ".vcpkg-root"); - } - } - - Checks::check_exit(!vcpkg_root_dir.empty(), "Error: Could not detect vcpkg-root."); - - const expected<vcpkg_paths> expected_paths = vcpkg_paths::create(vcpkg_root_dir); - Checks::check_exit(!expected_paths.error_code(), "Error: Invalid vcpkg root directory %s: %s", vcpkg_root_dir.string(), expected_paths.error_code().message()); - const vcpkg_paths paths = expected_paths.get_or_throw(); - int exit_code = _wchdir(paths.root.c_str()); - Checks::check_exit(exit_code == 0, "Changing the working dir failed"); - - if (auto command_function = find_command(args.command, get_available_commands_type_b())) - { - return command_function(args, paths); - } - - triplet default_target_triplet; - if (args.target_triplet != nullptr) - { - default_target_triplet = triplet::from_canonical_name(*args.target_triplet); - } - else - { - const auto vcpkg_default_triplet_env = System::wdupenv_str(L"VCPKG_DEFAULT_TRIPLET"); - if (!vcpkg_default_triplet_env.empty()) - { - default_target_triplet = triplet::from_canonical_name(Strings::utf16_to_utf8(vcpkg_default_triplet_env)); - } - else - { - default_target_triplet = triplet::X86_WINDOWS; - } - } - - Input::check_triplet(default_target_triplet, paths); - - if (auto command_function = find_command(args.command, get_available_commands_type_a())) - { - return command_function(args, paths, default_target_triplet); - } - - return invalid_command(args.command); -} - -static void loadConfig() -{ - fs::path localappdata; - { - // Config path in AppDataLocal - wchar_t* localappdatapath = nullptr; - if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localappdatapath)) - __fastfail(1); - localappdata = localappdatapath; - CoTaskMemFree(localappdatapath); - } - - try - { - std::string config_contents = Files::get_contents(localappdata / "vcpkg" / "config").get_or_throw(); - - std::unordered_map<std::string, std::string> keys; - auto pghs = parse_paragraphs(config_contents); - if (pghs.size() > 0) - keys = pghs[0]; - - for (size_t x = 1; x < pghs.size(); ++x) - { - for (auto&& p : pghs[x]) - keys.insert(p); - } - - auto user_id = keys["User-Id"]; - auto user_time = keys["User-Since"]; - Checks::check_throw(!user_id.empty() && !user_time.empty(), ""); // Use as goto to the catch statement - - SetUserInformation(user_id, user_time); - return; - } - catch (...) - { - } - - // config file not found, could not be read, or invalid - std::string user_id, user_time; - InitUserInformation(user_id, user_time); - SetUserInformation(user_id, user_time); - try - { - std::error_code ec; - fs::create_directory(localappdata / "vcpkg", ec); - std::ofstream(localappdata / "vcpkg" / "config", std::ios_base::out | std::ios_base::trunc) - << "User-Id: " << user_id << "\n" - << "User-Since: " << user_time << "\n"; - } - catch (...) - { - } -} - -static System::Stopwatch2 g_timer; - -static std::string trim_path_from_command_line(const std::string& full_command_line) -{ - Checks::check_exit(full_command_line.size() > 0, "Internal failure - cannot have empty command line"); - - if (full_command_line[0] == '"') - { - auto it = std::find(full_command_line.cbegin() + 1, full_command_line.cend(), '"'); - if (it != full_command_line.cend()) // Skip over the quote - ++it; - while (it != full_command_line.cend() && *it == ' ') // Skip over a space - ++it; - return std::string(it, full_command_line.cend()); - } - - auto it = std::find(full_command_line.cbegin(), full_command_line.cend(), ' '); - while (it != full_command_line.cend() && *it == ' ') - ++it; - return std::string(it, full_command_line.cend()); -} - -int wmain(const int argc, const wchar_t* const* const argv) -{ - if (argc == 0) - std::abort(); - - std::cout.sync_with_stdio(false); - std::cout.imbue(std::locale::classic()); - - g_timer.start(); - atexit([]() - { - g_timer.stop(); - TrackMetric("elapsed_us", g_timer.microseconds()); - Flush(); - }); - - TrackProperty("version", version()); - - const std::string trimmed_command_line = trim_path_from_command_line(Strings::utf16_to_utf8(GetCommandLineW())); - TrackProperty("cmdline", trimmed_command_line); - loadConfig(); - - const vcpkg_cmd_arguments args = vcpkg_cmd_arguments::create_from_command_line(argc, argv); - - if (args.printmetrics != opt_bool::unspecified) - SetPrintMetrics(args.printmetrics == opt_bool::enabled); - if (args.sendmetrics != opt_bool::unspecified) - SetSendMetrics(args.sendmetrics == opt_bool::enabled); - - if (args.debug != opt_bool::unspecified) - { - g_debugging = (args.debug == opt_bool::enabled); - } - - if (g_debugging) - { - inner(args); - exit(EXIT_FAILURE); - } - - std::string exc_msg; - try - { - inner(args); - exit(EXIT_FAILURE); - } - catch (std::exception& e) - { - exc_msg = e.what(); - } - catch (...) - { - exc_msg = "unknown error(...)"; - } - TrackProperty("error", exc_msg); - std::cerr - << "vcpkg.exe has crashed.\n" - << "Please send an email to:\n" - << " vcpkg@microsoft.com\n" - << "containing a brief summary of what you were trying to do and the following data blob:\n" - << "\n" - << "Version=" << version() << "\n" - << "EXCEPTION='" << exc_msg << "'\n" - << "CMD=\n"; - for (int x = 0; x < argc; ++x) - std::cerr << argv[x] << "|\n"; - std::cerr - << "\n"; -} diff --git a/toolsrc/src/metrics.cpp b/toolsrc/src/metrics.cpp index ada065fd6..263d6eb74 100644 --- a/toolsrc/src/metrics.cpp +++ b/toolsrc/src/metrics.cpp @@ -1,21 +1,9 @@ +#include "pch.h" #include "metrics.h" -#include <utility> -#include <array> -#include <string> -#include <iostream> -#include <vector> -#include <sys/timeb.h> -#include <time.h> -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#include <winhttp.h> -#include <fstream> -#include <filesystem> +#include "filesystem_fs.h" #include "vcpkg_Strings.h" #include "vcpkg_System.h" -namespace fs = std::tr2::sys; - namespace vcpkg { static std::string GetCurrentDateTime() @@ -237,6 +225,40 @@ true return DISABLE_METRICS == 0; } + std::wstring GetSQMUser() + { + LONG err; + + struct RAII_HKEY { + HKEY hkey = nullptr; + ~RAII_HKEY() + { + if (hkey != nullptr) + RegCloseKey(hkey); + } + } HKCU_SQMClient; + + err = RegOpenKeyExW(HKEY_CURRENT_USER, LR"(Software\Microsoft\SQMClient)", NULL, KEY_READ, &HKCU_SQMClient.hkey); + if (err != ERROR_SUCCESS) + { + return L"{}"; + } + + std::array<wchar_t,128> buffer; + DWORD lType = 0; + DWORD dwBufferSize = static_cast<DWORD>(buffer.size() * sizeof(wchar_t)); + err = RegQueryValueExW(HKCU_SQMClient.hkey, L"UserId", nullptr, &lType, reinterpret_cast<LPBYTE>(buffer.data()), &dwBufferSize); + if (err == ERROR_SUCCESS && lType == REG_SZ && dwBufferSize >= sizeof(wchar_t)) + { + size_t sz = dwBufferSize / sizeof(wchar_t); + if (buffer[sz - 1] == '\0') + --sz; + return std::wstring(buffer.begin(), buffer.begin() + sz); + } + + return L"{}"; + } + void SetUserInformation(const std::string& user_id, const std::string& first_use_time) { g_metricmessage.user_id = user_id; diff --git a/toolsrc/src/opt_bool.cpp b/toolsrc/src/opt_bool.cpp new file mode 100644 index 000000000..324936fb4 --- /dev/null +++ b/toolsrc/src/opt_bool.cpp @@ -0,0 +1,29 @@ +#include "pch.h" +#include "opt_bool.h" +#include "vcpkg_Checks.h" + +namespace vcpkg::opt_bool +{ + static const std::string UNSPECIFIED_NAME = "unspecified"; + static const std::string ENABLED_NAME = "enabled"; + static const std::string DISABLED_NAME = "disabled"; + type parse(const std::string& s) + { + if (s == UNSPECIFIED_NAME) + { + return opt_bool_t::UNSPECIFIED; + } + + if (s == ENABLED_NAME) + { + return opt_bool_t::ENABLED; + } + + if (s == DISABLED_NAME) + { + return opt_bool_t::DISABLED; + } + + Checks::exit_with_message("Could not convert string [%s] to opt_bool", s); + } +} diff --git a/toolsrc/src/package_spec.cpp b/toolsrc/src/package_spec.cpp index 86d4393bd..2713e219f 100644 --- a/toolsrc/src/package_spec.cpp +++ b/toolsrc/src/package_spec.cpp @@ -1,5 +1,5 @@ +#include "pch.h" #include "package_spec.h" -#include <algorithm> namespace vcpkg { @@ -50,19 +50,24 @@ namespace vcpkg return this->m_target_triplet; } + std::string package_spec::display_name() const + { + return Strings::format("%s:%s", this->name(), this->target_triplet()); + } + std::string package_spec::dir() const { return Strings::format("%s_%s", this->m_name, this->m_target_triplet); } - std::string to_string(const package_spec& spec) + std::string package_spec::toString() const { - return Strings::format("%s:%s", spec.name(), spec.target_triplet()); + return this->display_name(); } std::string to_printf_arg(const package_spec& spec) { - return to_string(spec); + return spec.toString(); } bool operator==(const package_spec& left, const package_spec& right) @@ -72,6 +77,6 @@ namespace vcpkg std::ostream& operator<<(std::ostream& os, const package_spec& spec) { - return os << to_string(spec); + return os << spec.toString(); } } diff --git a/toolsrc/src/package_spec_parse_result.cpp b/toolsrc/src/package_spec_parse_result.cpp index dc377f656..892232c2e 100644 --- a/toolsrc/src/package_spec_parse_result.cpp +++ b/toolsrc/src/package_spec_parse_result.cpp @@ -1,5 +1,5 @@ -#include <package_spec.h> -#include <system_error> +#include "pch.h" +#include "package_spec.h" #include "package_spec_parse_result.h" namespace vcpkg diff --git a/toolsrc/src/pch.cpp b/toolsrc/src/pch.cpp new file mode 100644 index 000000000..17305716a --- /dev/null +++ b/toolsrc/src/pch.cpp @@ -0,0 +1 @@ +#include "pch.h"
\ No newline at end of file diff --git a/toolsrc/src/post_build_lint.cpp b/toolsrc/src/post_build_lint.cpp deleted file mode 100644 index c68148fb2..000000000 --- a/toolsrc/src/post_build_lint.cpp +++ /dev/null @@ -1,433 +0,0 @@ -#include <filesystem> -#include "vcpkg_paths.h" -#include "package_spec.h" -#include <iterator> -#include <functional> -#include "vcpkg_System.h" -#include "coff_file_reader.h" - -namespace fs = std::tr2::sys; - -namespace vcpkg -{ - enum class lint_status - { - SUCCESS = 0, - ERROR_DETECTED = 1 - }; - - static const fs::path DUMPBIN_EXE = R"(%VS140COMNTOOLS%\..\..\VC\bin\dumpbin.exe)"; - - namespace - { - void print_vector_of_files(const std::vector<fs::path>& paths) - { - System::println(""); - for (const fs::path& p : paths) - { - System::println(" %s", p.generic_string()); - } - System::println(""); - } - - template <class Pred> - void recursive_find_matching_paths_in_dir(const fs::path& dir, const Pred predicate, std::vector<fs::path>& output) - { - std::copy_if(fs::recursive_directory_iterator(dir), fs::recursive_directory_iterator(), std::back_inserter(output), predicate); - } - - void recursive_find_files_with_extension_in_dir(const fs::path& dir, const std::string& extension, std::vector<fs::path>& output) - { - recursive_find_matching_paths_in_dir(dir, [&extension](const fs::path& current) - { - return !fs::is_directory(current) && current.extension() == extension; - }, output); - } - } - - static lint_status check_for_files_in_include_directory(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path include_dir = paths.packages / spec.dir() / "include"; - if (!fs::exists(include_dir) || fs::is_empty(include_dir)) - { - System::println(System::color::warning, "The folder /include is empty. This indicates the library was not correctly installed."); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_for_files_in_debug_include_directory(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path debug_include_dir = paths.packages / spec.dir() / "debug" / "include"; - std::vector<fs::path> files_found; - - recursive_find_matching_paths_in_dir(debug_include_dir, [&](const fs::path& current) - { - return !fs::is_directory(current) && current.extension() != ".ifc"; - }, files_found); - - if (!files_found.empty()) - { - System::println(System::color::warning, "Include files should not be duplicated into the /debug/include directory. If this cannot be disabled in the project cmake, use\n" - " file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)" - ); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_for_files_in_debug_share_directory(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path debug_share = paths.packages / spec.dir() / "debug" / "share"; - - if (fs::exists(debug_share) && !fs::is_empty(debug_share)) - { - System::println(System::color::warning, "No files should be present in /debug/share"); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_folder_lib_cmake(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path lib_cmake = paths.packages / spec.dir() / "lib" / "cmake"; - if (fs::exists(lib_cmake)) - { - System::println(System::color::warning, "The /lib/cmake folder should be moved to just /cmake"); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_for_misplaced_cmake_files(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path current_packages_dir = paths.packages / spec.dir(); - std::vector<fs::path> misplaced_cmake_files; - recursive_find_files_with_extension_in_dir(current_packages_dir / "cmake", ".cmake", misplaced_cmake_files); - recursive_find_files_with_extension_in_dir(current_packages_dir / "debug" / "cmake", ".cmake", misplaced_cmake_files); - recursive_find_files_with_extension_in_dir(current_packages_dir / "lib" / "cmake", ".cmake", misplaced_cmake_files); - recursive_find_files_with_extension_in_dir(current_packages_dir / "debug" / "lib" / "cmake", ".cmake", misplaced_cmake_files); - - if (!misplaced_cmake_files.empty()) - { - System::println(System::color::warning, "The following cmake files were found outside /share/%s. Please place cmake files in /share/%s.", spec.name(), spec.name()); - print_vector_of_files(misplaced_cmake_files); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_folder_debug_lib_cmake(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path lib_cmake_debug = paths.packages / spec.dir() / "debug" / "lib" / "cmake"; - if (fs::exists(lib_cmake_debug)) - { - System::println(System::color::warning, "The /debug/lib/cmake folder should be moved to just /debug/cmake"); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_for_dlls_in_lib_dirs(const package_spec& spec, const vcpkg_paths& paths) - { - std::vector<fs::path> dlls; - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "lib", ".dll", dlls); - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "lib", ".dll", dlls); - - if (!dlls.empty()) - { - System::println(System::color::warning, "\nThe following dlls were found in /lib and /debug/lib. Please move them to /bin or /debug/bin, respectively."); - print_vector_of_files(dlls); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_for_copyright_file(const package_spec& spec, const vcpkg_paths& paths) - { - const fs::path copyright_file = paths.packages / spec.dir() / "share" / spec.name() / "copyright"; - if (fs::exists(copyright_file)) - { - return lint_status::SUCCESS; - } - const fs::path current_buildtrees_dir = paths.buildtrees / spec.name(); - const fs::path current_buildtrees_dir_src = current_buildtrees_dir / "src"; - - std::vector<fs::path> potential_copyright_files; - // Only searching one level deep - for (auto it = fs::recursive_directory_iterator(current_buildtrees_dir_src); it != fs::recursive_directory_iterator(); ++it) - { - if (it.depth() > 1) - { - continue; - } - - const std::string filename = it->path().filename().string(); - if (filename == "LICENSE" || filename == "LICENSE.txt" || filename == "COPYING") - { - potential_copyright_files.push_back(it->path()); - } - } - - System::println(System::color::warning, "The software license must be available at ${CURRENT_PACKAGES_DIR}/share/%s/copyright .", spec.name()); - if (potential_copyright_files.size() == 1) // if there is only one candidate, provide the cmake lines needed to place it in the proper location - { - const fs::path found_file = potential_copyright_files[0]; - const fs::path relative_path = found_file.string().erase(0, current_buildtrees_dir.string().size() + 1); // The +1 is needed to remove the "/" - System::println("\n file(COPY ${CURRENT_BUILDTREES_DIR}/%s DESTINATION ${CURRENT_PACKAGES_DIR}/share/%s)\n" - " file(RENAME ${CURRENT_PACKAGES_DIR}/share/%s/%s ${CURRENT_PACKAGES_DIR}/share/%s/copyright)", - relative_path.generic_string(), spec.name(), spec.name(), found_file.filename().generic_string(), spec.name()); - return lint_status::ERROR_DETECTED; - } - - if (potential_copyright_files.size() > 1) - { - System::println(System::color::warning, "The following files are potential copyright files:"); - print_vector_of_files(potential_copyright_files); - } - - const fs::path current_packages_dir = paths.packages / spec.dir(); - System::println(" %s/share/%s/copyright", current_packages_dir.generic_string(), spec.name()); - - return lint_status::ERROR_DETECTED; - } - - static lint_status check_for_exes(const package_spec& spec, const vcpkg_paths& paths) - { - std::vector<fs::path> exes; - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "bin", ".exe", exes); - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "bin", ".exe", exes); - - if (!exes.empty()) - { - System::println(System::color::warning, "The following EXEs were found in /bin and /debug/bin. EXEs are not valid distribution targets."); - print_vector_of_files(exes); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_exports_of_dlls(const std::vector<fs::path>& dlls) - { - std::vector<fs::path> dlls_with_no_exports; - for (const fs::path& dll : dlls) - { - const std::wstring cmd_line = Strings::wformat(LR"("%s" /exports "%s")", DUMPBIN_EXE.native(), dll.native()); - System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); - - if (ec_data.output.find("ordinal hint RVA name") == std::string::npos) - { - dlls_with_no_exports.push_back(dll); - } - } - - if (!dlls_with_no_exports.empty()) - { - System::println(System::color::warning, "The following DLLs have no exports:"); - print_vector_of_files(dlls_with_no_exports); - System::println(System::color::warning, "DLLs without any exports are likely a bug in the build script."); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_uwp_bit_of_dlls(const std::string& expected_system_name, const std::vector<fs::path>& dlls) - { - if (expected_system_name != "uwp") - { - return lint_status::SUCCESS; - } - - std::vector<fs::path> dlls_with_improper_uwp_bit; - for (const fs::path& dll : dlls) - { - const std::wstring cmd_line = Strings::wformat(LR"("%s" /headers "%s")", DUMPBIN_EXE.native(), dll.native()); - System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd_line); - Checks::check_exit(ec_data.exit_code == 0, "Running command:\n %s\n failed", Strings::utf16_to_utf8(cmd_line)); - - if (ec_data.output.find("App Container") == std::string::npos) - { - dlls_with_improper_uwp_bit.push_back(dll); - } - } - - if (!dlls_with_improper_uwp_bit.empty()) - { - System::println(System::color::warning, "The following DLLs do not have the App Container bit set:"); - print_vector_of_files(dlls_with_improper_uwp_bit); - System::println(System::color::warning, "This bit is required for Windows Store apps."); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - struct file_and_arch - { - fs::path file; - std::string actual_arch; - }; - - static std::string get_actual_architecture(const MachineType& machine_type) - { - switch (machine_type) - { - case MachineType::AMD64: - case MachineType::IA64: - return "x64"; - case MachineType::I386: - return "x86"; - case MachineType::ARM: - case MachineType::ARMNT: - return "arm"; - default: - return "Machine Type Code = " + std::to_string(static_cast<uint16_t>(machine_type)); - } - } - - static void print_invalid_architecture_files(const std::string& expected_architecture, std::vector<file_and_arch> binaries_with_invalid_architecture) - { - System::println(System::color::warning, "The following files were built for an incorrect architecture:"); - System::println(""); - for (const file_and_arch& b : binaries_with_invalid_architecture) - { - System::println(" %s", b.file.generic_string()); - System::println("Expected %s, but was: %s", expected_architecture, b.actual_arch); - System::println(""); - } - } - - static lint_status check_dll_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) - { - std::vector<file_and_arch> binaries_with_invalid_architecture; - - for (const fs::path& file : files) - { - Checks::check_exit(file.extension() == ".dll", "The file extension was not .dll: %s", file.generic_string()); - COFFFileReader::dll_info info = COFFFileReader::read_dll(file); - const std::string actual_architecture = get_actual_architecture(info.machine_type); - - if (expected_architecture != actual_architecture) - { - binaries_with_invalid_architecture.push_back({file, actual_architecture}); - } - } - - if (!binaries_with_invalid_architecture.empty()) - { - print_invalid_architecture_files(expected_architecture, binaries_with_invalid_architecture); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_lib_architecture(const std::string& expected_architecture, const std::vector<fs::path>& files) - { - std::vector<file_and_arch> binaries_with_invalid_architecture; - - for (const fs::path& file : files) - { - Checks::check_exit(file.extension() == ".lib", "The file extension was not .lib: %s", file.generic_string()); - COFFFileReader::lib_info info = COFFFileReader::read_lib(file); - Checks::check_exit(info.machine_types.size() == 1, "Found more than 1 architecture in file %s", file.generic_string()); - - const std::string actual_architecture = get_actual_architecture(info.machine_types.at(0)); - if (expected_architecture != actual_architecture) - { - binaries_with_invalid_architecture.push_back({file, actual_architecture}); - } - } - - if (!binaries_with_invalid_architecture.empty()) - { - print_invalid_architecture_files(expected_architecture, binaries_with_invalid_architecture); - return lint_status::ERROR_DETECTED; - } - - return lint_status::SUCCESS; - } - - static lint_status check_no_dlls_present(const std::vector<fs::path>& dlls) - { - if (dlls.empty()) - { - return lint_status::SUCCESS; - } - - System::println(System::color::warning, "DLLs should not be present in a static build, but the following DLLs were found:"); - print_vector_of_files(dlls); - return lint_status::ERROR_DETECTED; - } - - static void operator +=(size_t& left, const lint_status& right) - { - left += static_cast<size_t>(right); - } - - void perform_all_checks(const package_spec& spec, const vcpkg_paths& paths) - { - System::println("-- Performing post-build validation"); - size_t error_count = 0; - error_count += check_for_files_in_include_directory(spec, paths); - error_count += check_for_files_in_debug_include_directory(spec, paths); - error_count += check_for_files_in_debug_share_directory(spec, paths); - error_count += check_folder_lib_cmake(spec, paths); - error_count += check_for_misplaced_cmake_files(spec, paths); - error_count += check_folder_debug_lib_cmake(spec, paths); - error_count += check_for_dlls_in_lib_dirs(spec, paths); - error_count += check_for_copyright_file(spec, paths); - error_count += check_for_exes(spec, paths); - - triplet::BuildType build_type = spec.target_triplet().build_type(); - switch (build_type) - { - case triplet::BuildType::DYNAMIC: - { - std::vector<fs::path> dlls; - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "bin", ".dll", dlls); - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "bin", ".dll", dlls); - - error_count += check_exports_of_dlls(dlls); - error_count += check_uwp_bit_of_dlls(spec.target_triplet().system(), dlls); - error_count += check_dll_architecture(spec.target_triplet().architecture(), dlls); - break; - } - case triplet::BuildType::STATIC: - { - std::vector<fs::path> dlls; - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir(), ".dll", dlls); - error_count += check_no_dlls_present(dlls); - - break; - } - - default: - Checks::unreachable(); - } - - std::vector<fs::path> libs; - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "lib", ".lib", libs); - recursive_find_files_with_extension_in_dir(paths.packages / spec.dir() / "debug" / "lib", ".lib", libs); - error_count += check_lib_architecture(spec.target_triplet().architecture(), libs); - - if (error_count != 0) - { - const fs::path portfile = paths.ports / spec.name() / "portfile.cmake"; - System::println(System::color::error, "Found %u error(s). Please correct the portfile:\n %s", error_count, portfile.string()); - exit(EXIT_FAILURE); - } - - System::println("-- Performing post-build validation done"); - } -} diff --git a/toolsrc/src/tests_dependencies.cpp b/toolsrc/src/tests_dependencies.cpp new file mode 100644 index 000000000..bce1cab0e --- /dev/null +++ b/toolsrc/src/tests_dependencies.cpp @@ -0,0 +1,39 @@ +#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 +{ + TEST_CLASS(DependencyTests) + { + public: + 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/test.cpp b/toolsrc/src/tests_paragraph.cpp index fc49b362d..fb20eee82 100644 --- a/toolsrc/src/test.cpp +++ b/toolsrc/src/tests_paragraph.cpp @@ -1,19 +1,20 @@ #include "CppUnitTest.h" -#include "vcpkg.h" +#include "Paragraphs.h" +#include "BinaryParagraph.h" #pragma comment(lib,"version") #pragma comment(lib,"winhttp") using namespace Microsoft::VisualStudio::CppUnitTestFramework; -namespace Microsoft { namespace VisualStudio { namespace CppUnitTestFramework +namespace Microsoft::VisualStudio::CppUnitTestFramework { template <> inline std::wstring ToString<vcpkg::package_spec_parse_result>(const vcpkg::package_spec_parse_result& t) { return ToString(static_cast<uint32_t>(t)); } -}}} +} namespace UnitTest1 { @@ -48,7 +49,7 @@ namespace UnitTest1 Assert::AreEqual("m", pgh.maintainer.c_str()); Assert::AreEqual("d", pgh.description.c_str()); Assert::AreEqual(size_t(1), pgh.depends.size()); - Assert::AreEqual("bd", pgh.depends[0].c_str()); + Assert::AreEqual("bd", pgh.depends[0].name.c_str()); } TEST_METHOD(SourceParagraph_Two_Depends) @@ -60,8 +61,8 @@ namespace UnitTest1 }); Assert::AreEqual(size_t(2), pgh.depends.size()); - Assert::AreEqual("z", pgh.depends[0].c_str()); - Assert::AreEqual("openssl", pgh.depends[1].c_str()); + Assert::AreEqual("z", pgh.depends[0].name.c_str()); + Assert::AreEqual("openssl", pgh.depends[1].name.c_str()); } TEST_METHOD(SourceParagraph_Three_Depends) @@ -73,9 +74,28 @@ namespace UnitTest1 }); Assert::AreEqual(size_t(3), pgh.depends.size()); - Assert::AreEqual("z", pgh.depends[0].c_str()); - Assert::AreEqual("openssl", pgh.depends[1].c_str()); - Assert::AreEqual("xyz", pgh.depends[2].c_str()); + 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_Construct_Qualified_Depends) + { + vcpkg::SourceParagraph pgh({ + {"Source", "zlib"}, + {"Version", "1.2.8"}, + {"Build-Depends", "libA [windows], libB [uwp]"} + }); + + 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(BinaryParagraph_Construct_Minimum) @@ -83,7 +103,7 @@ namespace UnitTest1 vcpkg::BinaryParagraph pgh({ {"Package", "zlib"}, {"Version", "1.2.8"}, - {"Architecture", "a"}, + {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, }); @@ -91,7 +111,7 @@ namespace UnitTest1 Assert::AreEqual("1.2.8", pgh.version.c_str()); Assert::AreEqual("", pgh.maintainer.c_str()); Assert::AreEqual("", pgh.description.c_str()); - Assert::AreEqual("a", pgh.spec.target_triplet().canonical_name().c_str()); + Assert::AreEqual("x86-windows", pgh.spec.target_triplet().canonical_name().c_str()); Assert::AreEqual(size_t(0), pgh.depends.size()); } @@ -100,7 +120,7 @@ namespace UnitTest1 vcpkg::BinaryParagraph pgh({ {"Package", "s"}, {"Version", "v"}, - {"Architecture", "a"}, + {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, {"Maintainer", "m"}, {"Description", "d"}, @@ -119,7 +139,7 @@ namespace UnitTest1 vcpkg::BinaryParagraph pgh({ {"Package", "zlib"}, {"Version", "1.2.8"}, - {"Architecture", "a"}, + {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, {"Depends", "a, b, c"}, }); @@ -133,14 +153,14 @@ namespace UnitTest1 TEST_METHOD(parse_paragraphs_empty) { const char* str = ""; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::IsTrue(pghs.empty()); } TEST_METHOD(parse_paragraphs_one_field) { const char* str = "f1: v1"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual(size_t(1), pghs[0].size()); Assert::AreEqual("v1", pghs[0]["f1"].c_str()); @@ -151,7 +171,7 @@ namespace UnitTest1 const char* str = "f1: v1\n" "f2: v2"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual(size_t(2), pghs[0].size()); Assert::AreEqual("v1", pghs[0]["f1"].c_str()); @@ -166,7 +186,7 @@ namespace UnitTest1 "\n" "f3: v3\n" "f4: v4"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(2), pghs.size()); Assert::AreEqual(size_t(2), pghs[0].size()); Assert::AreEqual("v1", pghs[0]["f1"].c_str()); @@ -184,7 +204,7 @@ namespace UnitTest1 "F:\n" "0:\n" "F-2:\n"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual(size_t(5), pghs[0].size()); } @@ -198,7 +218,7 @@ namespace UnitTest1 "\n" "f3: v3\n" "f4: v4"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(2), pghs.size()); } @@ -207,7 +227,7 @@ namespace UnitTest1 const char* str = "f1:\n" "f2: "; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual(size_t(2), pghs[0].size()); Assert::AreEqual("", pghs[0]["f1"].c_str()); @@ -223,7 +243,7 @@ namespace UnitTest1 "f2:\r\n" " f2\r\n" " continue\r\n"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual("simple\n f1", pghs[0]["f1"].c_str()); Assert::AreEqual("\n f2\n continue", pghs[0]["f2"].c_str()); @@ -237,7 +257,7 @@ namespace UnitTest1 "\r\n" "f3: v3\r\n" "f4: v4"; - auto pghs = vcpkg::parse_paragraphs(str); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(str); Assert::AreEqual(size_t(2), pghs.size()); Assert::AreEqual(size_t(2), pghs[0].size()); Assert::AreEqual("v1", pghs[0]["f1"].c_str()); @@ -253,16 +273,16 @@ namespace UnitTest1 vcpkg::BinaryParagraph pgh({ {"Package", "zlib"}, {"Version", "1.2.8"}, - {"Architecture", "a"}, + {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, }); ss << pgh; - auto pghs = vcpkg::parse_paragraphs(ss.str()); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss.str()); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual(size_t(4), pghs[0].size()); Assert::AreEqual("zlib", pghs[0]["Package"].c_str()); Assert::AreEqual("1.2.8", pghs[0]["Version"].c_str()); - Assert::AreEqual("a", pghs[0]["Architecture"].c_str()); + Assert::AreEqual("x86-windows", pghs[0]["Architecture"].c_str()); Assert::AreEqual("same", pghs[0]["Multi-Arch"].c_str()); } @@ -272,19 +292,19 @@ namespace UnitTest1 vcpkg::BinaryParagraph pgh({ {"Package", "zlib"}, {"Version", "1.2.8"}, - {"Architecture", "a"}, + {"Architecture", "x86-windows"}, {"Description", "first line\n second line"}, {"Maintainer", "abc <abc@abc.abc>"}, {"Depends", "dep"}, {"Multi-Arch", "same"}, }); ss << pgh; - auto pghs = vcpkg::parse_paragraphs(ss.str()); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss.str()); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual(size_t(7), pghs[0].size()); Assert::AreEqual("zlib", pghs[0]["Package"].c_str()); Assert::AreEqual("1.2.8", pghs[0]["Version"].c_str()); - Assert::AreEqual("a", pghs[0]["Architecture"].c_str()); + Assert::AreEqual("x86-windows", pghs[0]["Architecture"].c_str()); Assert::AreEqual("same", pghs[0]["Multi-Arch"].c_str()); Assert::AreEqual("first line\n second line", pghs[0]["Description"].c_str()); Assert::AreEqual("dep", pghs[0]["Depends"].c_str()); @@ -296,12 +316,12 @@ namespace UnitTest1 vcpkg::BinaryParagraph pgh({ {"Package", "zlib"}, {"Version", "1.2.8"}, - {"Architecture", "a"}, + {"Architecture", "x86-windows"}, {"Multi-Arch", "same"}, {"Depends", "a, b, c"}, }); ss << pgh; - auto pghs = vcpkg::parse_paragraphs(ss.str()); + auto pghs = vcpkg::Paragraphs::parse_paragraphs(ss.str()); Assert::AreEqual(size_t(1), pghs.size()); Assert::AreEqual("a, b, c", pghs[0]["Depends"].c_str()); } diff --git a/toolsrc/src/triplet.cpp b/toolsrc/src/triplet.cpp index af2ca2a72..e1302d9ed 100644 --- a/toolsrc/src/triplet.cpp +++ b/toolsrc/src/triplet.cpp @@ -1,6 +1,6 @@ +#include "pch.h" #include "triplet.h" #include "vcpkg_Checks.h" -#include <algorithm> namespace vcpkg { @@ -64,14 +64,4 @@ namespace vcpkg auto it = std::find(this->m_canonical_name.cbegin(), this->m_canonical_name.cend(), '-'); return std::string(it + 1, this->m_canonical_name.cend()); } - - triplet::BuildType triplet::build_type() const - { - if (this->m_canonical_name.find("static") != std::string::npos) - { - return BuildType::STATIC; - } - - return BuildType::DYNAMIC; - } } diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index f705858cc..3e313c702 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -1,178 +1,249 @@ -#include "vcpkg.h" -#include <regex> +#define WIN32_LEAN_AND_MEAN +#include <Windows.h> + +#include <iostream> +#include <fstream> +#include <memory> +#include <cassert> +#include "vcpkg_Commands.h" +#include "metrics.h" +#include <Shlobj.h> #include "vcpkg_Files.h" -#include "vcpkglib_helpers.h" +#include "vcpkg_System.h" +#include "vcpkg_Input.h" +#include "Paragraphs.h" +#include "vcpkg_info.h" +#include "vcpkg_Strings.h" -namespace +using namespace vcpkg; + +bool g_debugging = false; + +void invalid_command(const std::string& cmd) { - using namespace vcpkg; + System::println(System::color::error, "invalid command: %s", cmd); + Commands::Help::print_usage(); + exit(EXIT_FAILURE); +} - struct Parser +static void inner(const vcpkg_cmd_arguments& args) +{ + TrackProperty("command", args.command); + if (args.command.empty()) { - Parser(const char* c, const char* e) : cur(c), end(e) - { - } + Commands::Help::print_usage(); + exit(EXIT_FAILURE); + } - private: - const char* cur; - const char* const end; + if (auto command_function = Commands::find(args.command, Commands::get_available_commands_type_c())) + { + return command_function(args); + } - void peek(char& ch) const - { - if (cur == end) - ch = 0; - else - ch = *cur; - } + fs::path vcpkg_root_dir; + if (args.vcpkg_root_dir != nullptr) + { + vcpkg_root_dir = fs::absolute(Strings::utf8_to_utf16(*args.vcpkg_root_dir)); + } + else + { + auto vcpkg_root_dir_env = System::wdupenv_str(L"VCPKG_ROOT"); - void next(char& ch) + if (!vcpkg_root_dir_env.empty()) { - if (cur == end) - ch = 0; - else - { - ++cur; - peek(ch); - } + vcpkg_root_dir = fs::absolute(vcpkg_root_dir_env); } - - void skip_spaces(char& ch) + else { - while (ch == ' ' || ch == '\t') - next(ch); + vcpkg_root_dir = Files::find_file_recursively_up(fs::absolute(System::get_exe_path_of_current_process()), ".vcpkg-root"); } + } - static bool is_alphanum(char ch) - { - return (ch >= 'A' && ch <= 'Z') - || (ch >= 'a' && ch <= 'z') - || (ch >= '0' && ch <= '9'); - } + Checks::check_exit(!vcpkg_root_dir.empty(), "Error: Could not detect vcpkg-root."); - static bool is_lineend(char ch) - { - return ch == '\r' || ch == '\n' || ch == 0; - } + const expected<vcpkg_paths> expected_paths = vcpkg_paths::create(vcpkg_root_dir); + Checks::check_exit(!expected_paths.error_code(), "Error: Invalid vcpkg root directory %s: %s", vcpkg_root_dir.string(), expected_paths.error_code().message()); + const vcpkg_paths paths = expected_paths.get_or_throw(); + int exit_code = _wchdir(paths.root.c_str()); + Checks::check_exit(exit_code == 0, "Changing the working dir failed"); - void get_fieldvalue(char& ch, std::string& fieldvalue) + if (auto command_function = Commands::find(args.command, Commands::get_available_commands_type_b())) + { + return command_function(args, paths); + } + + triplet default_target_triplet; + if (args.target_triplet != nullptr) + { + default_target_triplet = triplet::from_canonical_name(*args.target_triplet); + } + else + { + const auto vcpkg_default_triplet_env = System::wdupenv_str(L"VCPKG_DEFAULT_TRIPLET"); + if (!vcpkg_default_triplet_env.empty()) { - fieldvalue.clear(); - - auto beginning_of_line = cur; - do - { - // scan to end of current line (it is part of the field value) - while (!is_lineend(ch)) - next(ch); - - fieldvalue.append(beginning_of_line, cur); - - if (ch == '\r') - next(ch); - if (ch == '\n') - next(ch); - - if (is_alphanum(ch)) - { - // Line begins a new field. - return; - } - - beginning_of_line = cur; - - // Line may continue the current field with data or terminate the paragraph, - // depending on first nonspace character. - skip_spaces(ch); - - if (is_lineend(ch)) - { - // Line was whitespace or empty. - // This terminates the field and the paragraph. - // We leave the blank line's whitespace consumed, because it doesn't matter. - return; - } - - // First nonspace is not a newline. This continues the current field value. - // We forcibly convert all newlines into single '\n' for ease of text handling later on. - fieldvalue.push_back('\n'); - } - while (true); + default_target_triplet = triplet::from_canonical_name(Strings::utf16_to_utf8(vcpkg_default_triplet_env)); } - - void get_fieldname(char& ch, std::string& fieldname) + else { - auto begin_fieldname = cur; - while (is_alphanum(ch) || ch == '-') - next(ch); - Checks::check_throw(ch == ':', "Expected ':'"); - fieldname = std::string(begin_fieldname, cur); - - // skip ': ' - next(ch); - skip_spaces(ch); + default_target_triplet = triplet::X86_WINDOWS; } + } - void get_paragraph(char& ch, std::unordered_map<std::string, std::string>& fields) - { - fields.clear(); - std::string fieldname; - std::string fieldvalue; - do - { - get_fieldname(ch, fieldname); + Input::check_triplet(default_target_triplet, paths); - auto it = fields.find(fieldname); - Checks::check_throw(it == fields.end(), "Duplicate field"); + if (auto command_function = Commands::find(args.command, Commands::get_available_commands_type_a())) + { + return command_function(args, paths, default_target_triplet); + } - get_fieldvalue(ch, fieldvalue); + return invalid_command(args.command); +} - fields.emplace(fieldname, fieldvalue); - } - while (!is_lineend(ch)); - } +static void loadConfig() +{ + fs::path localappdata; + { + // Config path in AppDataLocal + wchar_t* localappdatapath = nullptr; + if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localappdatapath)) + __fastfail(1); + localappdata = localappdatapath; + CoTaskMemFree(localappdatapath); + } + + try + { + std::string config_contents = Files::read_contents(localappdata / "vcpkg" / "config").get_or_throw(); + + std::unordered_map<std::string, std::string> keys; + auto pghs = Paragraphs::parse_paragraphs(config_contents); + if (pghs.size() > 0) + keys = pghs[0]; - public: - std::vector<std::unordered_map<std::string, std::string>> get_paragraphs() + for (size_t x = 1; x < pghs.size(); ++x) { - std::vector<std::unordered_map<std::string, std::string>> paragraphs; + for (auto&& p : pghs[x]) + keys.insert(p); + } - char ch; - peek(ch); + auto user_id = keys["User-Id"]; + auto user_time = keys["User-Since"]; + Checks::check_throw(!user_id.empty() && !user_time.empty(), ""); // Use as goto to the catch statement - while (ch != 0) - { - if (ch == '\n' || ch == '\r' || ch == ' ' || ch == '\t') - { - next(ch); - continue; - } + SetUserInformation(user_id, user_time); + return; + } + catch (...) + { + } - paragraphs.emplace_back(); - get_paragraph(ch, paragraphs.back()); - } + // config file not found, could not be read, or invalid + std::string user_id, user_time; + InitUserInformation(user_id, user_time); + SetUserInformation(user_id, user_time); + try + { + std::error_code ec; + fs::create_directory(localappdata / "vcpkg", ec); + std::ofstream(localappdata / "vcpkg" / "config", std::ios_base::out | std::ios_base::trunc) + << "User-Id: " << user_id << "\n" + << "User-Since: " << user_time << "\n"; + } + catch (...) + { + } +} - return paragraphs; - } - }; +static System::Stopwatch2 g_timer; + +static std::string trim_path_from_command_line(const std::string& full_command_line) +{ + Checks::check_exit(full_command_line.size() > 0, "Internal failure - cannot have empty command line"); + + if (full_command_line[0] == '"') + { + auto it = std::find(full_command_line.cbegin() + 1, full_command_line.cend(), '"'); + if (it != full_command_line.cend()) // Skip over the quote + ++it; + while (it != full_command_line.cend() && *it == ' ') // Skip over a space + ++it; + return std::string(it, full_command_line.cend()); + } + + auto it = std::find(full_command_line.cbegin(), full_command_line.cend(), ' '); + while (it != full_command_line.cend() && *it == ' ') + ++it; + return std::string(it, full_command_line.cend()); } -namespace vcpkg +int wmain(const int argc, const wchar_t* const* const argv) { - std::string shorten_description(const std::string& desc) + if (argc == 0) + std::abort(); + + std::cout.sync_with_stdio(false); + std::cout.imbue(std::locale::classic()); + + g_timer.start(); + atexit([]() + { + g_timer.stop(); + TrackMetric("elapsed_us", g_timer.microseconds()); + Flush(); + }); + + TrackProperty("version", Info::version()); + + const std::string trimmed_command_line = trim_path_from_command_line(Strings::utf16_to_utf8(GetCommandLineW())); + TrackProperty("cmdline", trimmed_command_line); + loadConfig(); + TrackProperty("sqmuser", GetSQMUser()); + + const vcpkg_cmd_arguments args = vcpkg_cmd_arguments::create_from_command_line(argc, argv); + + if (args.printmetrics != opt_bool_t::UNSPECIFIED) + SetPrintMetrics(args.printmetrics == opt_bool_t::ENABLED); + if (args.sendmetrics != opt_bool_t::UNSPECIFIED) + SetSendMetrics(args.sendmetrics == opt_bool_t::ENABLED); + + if (args.debug != opt_bool_t::UNSPECIFIED) { - auto simple_desc = std::regex_replace(desc.substr(0, 49), std::regex("\\n( |\\t)?"), ""); - if (desc.size() > 49) - simple_desc.append("..."); - return simple_desc; + g_debugging = (args.debug == opt_bool_t::ENABLED); } - std::vector<std::unordered_map<std::string, std::string>> get_paragraphs(const fs::path& control_path) + if (g_debugging) { - return parse_paragraphs(Files::get_contents(control_path).get_or_throw()); + inner(args); + exit(EXIT_FAILURE); } - std::vector<std::unordered_map<std::string, std::string>> parse_paragraphs(const std::string& str) + std::string exc_msg; + try + { + inner(args); + exit(EXIT_FAILURE); + } + catch (std::exception& e) + { + exc_msg = e.what(); + } + catch (...) { - return Parser(str.c_str(), str.c_str() + str.size()).get_paragraphs(); + exc_msg = "unknown error(...)"; } + TrackProperty("error", exc_msg); + std::cerr + << "vcpkg.exe has crashed.\n" + << "Please send an email to:\n" + << " " << Info::email() << "\n" + << "containing a brief summary of what you were trying to do and the following data blob:\n" + << "\n" + << "Version=" << Info::version() << "\n" + << "EXCEPTION='" << exc_msg << "'\n" + << "CMD=\n"; + for (int x = 0; x < argc; ++x) + std::cerr << Strings::utf16_to_utf8(argv[x]) << "|\n"; + std::cerr + << "\n"; } diff --git a/toolsrc/src/vcpkg_Checks.cpp b/toolsrc/src/vcpkg_Checks.cpp index db6c03480..5c3fef27a 100644 --- a/toolsrc/src/vcpkg_Checks.cpp +++ b/toolsrc/src/vcpkg_Checks.cpp @@ -1,14 +1,17 @@ +#include "pch.h" #include "vcpkg_Checks.h" - -#include <stdexcept> #include "vcpkg_System.h" -namespace vcpkg {namespace Checks +namespace vcpkg::Checks { void unreachable() { System::println(System::color::error, "Error: Unreachable code was reached"); +#ifndef NDEBUG + std::abort(); +#else exit(EXIT_FAILURE); +#endif } void exit_with_message(const char* errorMessage) @@ -37,4 +40,4 @@ namespace vcpkg {namespace Checks exit_with_message(errorMessage); } } -}} +} diff --git a/toolsrc/src/vcpkg_Dependencies.cpp b/toolsrc/src/vcpkg_Dependencies.cpp index 54b37cd11..5bd6c3eb9 100644 --- a/toolsrc/src/vcpkg_Dependencies.cpp +++ b/toolsrc/src/vcpkg_Dependencies.cpp @@ -1,23 +1,111 @@ +#include "pch.h" #include "vcpkg_Dependencies.h" -#include <vector> #include "vcpkg_Graphs.h" #include "vcpkg_paths.h" #include "package_spec.h" #include "StatusParagraphs.h" -#include <unordered_set> -#include "vcpkg.h" -#include "vcpkg_Maps.h" -#include "vcpkg_Sets.h" +#include "vcpkg_Files.h" +#include "vcpkglib.h" -namespace vcpkg { namespace Dependencies +namespace vcpkg::Dependencies { - static Graphs::Graph<package_spec> build_dependency_graph(const vcpkg_paths& paths, const std::vector<package_spec>& specs, const StatusParagraphs& status_db) + install_plan_action::install_plan_action() : plan_type(install_plan_type::UNKNOWN), binary_pgh(nullptr), source_pgh(nullptr) { + } + + install_plan_action::install_plan_action(const install_plan_type& plan_type, optional<BinaryParagraph> binary_pgh, optional<SourceParagraph> source_pgh) + : plan_type(std::move(plan_type)), binary_pgh(std::move(binary_pgh)), source_pgh(std::move(source_pgh)) + { + } + + package_spec_with_install_plan::package_spec_with_install_plan(const package_spec& spec, install_plan_action&& plan) : spec(spec), plan(std::move(plan)) + { + } + + remove_plan_action::remove_plan_action() : plan_type(remove_plan_type::UNKNOWN), request_type(request_type::UNKNOWN) + { + } + + remove_plan_action::remove_plan_action(const remove_plan_type& plan_type, const Dependencies::request_type& request_type) : plan_type(plan_type), request_type(request_type) + { + } + + package_spec_with_remove_plan::package_spec_with_remove_plan(const package_spec& spec, remove_plan_action&& plan) + : spec(spec), plan(std::move(plan)) + { + } + + std::vector<package_spec_with_install_plan> create_install_plan(const vcpkg_paths& paths, const std::vector<package_spec>& specs, const StatusParagraphs& status_db) + { + std::unordered_map<package_spec, install_plan_action> was_examined; // Examine = we have checked its immediate (non-recursive) dependencies + Graphs::Graph<package_spec> graph; + graph.add_vertices(specs); + std::vector<package_spec> examine_stack(specs); - std::unordered_set<package_spec> was_examined; // Examine = we have checked its immediate (non-recursive) dependencies + while (!examine_stack.empty()) + { + const package_spec spec = examine_stack.back(); + examine_stack.pop_back(); + + if (was_examined.find(spec) != was_examined.end()) + { + continue; + } + + auto process_dependencies = [&](const std::vector<std::string>& dependencies_as_string) + { + for (const std::string& dep_as_string : dependencies_as_string) + { + const package_spec current_dep = package_spec::from_name_and_triplet(dep_as_string, spec.target_triplet()).get_or_throw(); + graph.add_edge(spec, current_dep); + if (was_examined.find(current_dep) == was_examined.end()) + { + examine_stack.push_back(std::move(current_dep)); + } + } + }; + + auto it = status_db.find(spec); + if (it != status_db.end() && (*it)->want == want_t::install) + { + was_examined.emplace(spec, install_plan_action{install_plan_type::ALREADY_INSTALLED, nullptr, nullptr}); + continue; + } + + expected<BinaryParagraph> maybe_bpgh = try_load_cached_package(paths, spec); + if (BinaryParagraph* bpgh = maybe_bpgh.get()) + { + process_dependencies(bpgh->depends); + was_examined.emplace(spec, install_plan_action{install_plan_type::INSTALL, std::make_unique<BinaryParagraph>(std::move(*bpgh)), nullptr}); + continue; + } + + expected<SourceParagraph> maybe_spgh = try_load_port(paths, spec.name()); + SourceParagraph* spgh = maybe_spgh.get(); + Checks::check_exit(spgh != nullptr, "Cannot find package %s", spec.name()); + process_dependencies(filter_dependencies(spgh->depends, spec.target_triplet())); + was_examined.emplace(spec, install_plan_action{install_plan_type::BUILD_AND_INSTALL, nullptr, std::make_unique<SourceParagraph>(std::move(*spgh))}); + } + + std::vector<package_spec_with_install_plan> ret; + + const std::vector<package_spec> pkgs = graph.find_topological_sort(); + for (const package_spec& pkg : pkgs) + { + ret.push_back(package_spec_with_install_plan(pkg, std::move(was_examined[pkg]))); + } + return ret; + } + + std::vector<package_spec_with_remove_plan> create_remove_plan(const vcpkg_paths& paths, const std::vector<package_spec>& specs, const StatusParagraphs& status_db) + { + std::unordered_set<package_spec> specs_as_set(specs.cbegin(), specs.cend()); + + std::unordered_map<package_spec, remove_plan_action> was_examined; // Examine = we have checked its immediate (non-recursive) dependencies Graphs::Graph<package_spec> graph; - graph.add_vertices(examine_stack); + graph.add_vertices(specs); + std::vector<package_spec> examine_stack(specs); while (!examine_stack.empty()) { const package_spec spec = examine_stack.back(); @@ -28,40 +116,41 @@ namespace vcpkg { namespace Dependencies continue; } - std::vector<std::string> dependencies_as_string = get_unmet_package_dependencies(paths, spec, status_db); + const StatusParagraphs::const_iterator it = status_db.find(spec); + if (it == status_db.end() || (*it)->state == install_state_t::not_installed) + { + was_examined.emplace(spec, remove_plan_action(remove_plan_type::NOT_INSTALLED, request_type::USER_REQUESTED)); + continue; + } - for (const std::string& dep_as_string : dependencies_as_string) + for (const std::unique_ptr<StatusParagraph>& an_installed_package : status_db) { - const package_spec current_dep = package_spec::from_name_and_triplet(dep_as_string, spec.target_triplet()).get_or_throw(); - auto it = status_db.find(current_dep.name(), current_dep.target_triplet()); - if (it != status_db.end() && (*it)->want == want_t::install) - { + if (an_installed_package->want != want_t::install) + continue; + if (an_installed_package->package.spec.target_triplet() != spec.target_triplet()) continue; - } - graph.add_edge(spec, current_dep); - if (was_examined.find(current_dep) == was_examined.end()) + const std::vector<std::string>& deps = an_installed_package->package.depends; + if (std::find(deps.begin(), deps.end(), spec.name()) == deps.end()) { - examine_stack.push_back(std::move(current_dep)); + continue; } + + graph.add_edge(spec, an_installed_package.get()->package.spec); + examine_stack.push_back(an_installed_package.get()->package.spec); } - was_examined.insert(spec); + const request_type request_type = specs_as_set.find(spec) != specs_as_set.end() ? request_type::USER_REQUESTED : request_type::AUTO_SELECTED; + was_examined.emplace(spec, remove_plan_action(remove_plan_type::REMOVE, request_type)); } - return graph; - } - - std::vector<package_spec> create_dependency_ordered_install_plan(const vcpkg_paths& paths, const std::vector<package_spec>& specs, const StatusParagraphs& status_db) - { - return build_dependency_graph(paths, specs, status_db).find_topological_sort(); - } + std::vector<package_spec_with_remove_plan> ret; - std::unordered_set<package_spec> find_unmet_dependencies(const vcpkg_paths& paths, const package_spec& spec, const StatusParagraphs& status_db) - { - const Graphs::Graph<package_spec> dependency_graph = build_dependency_graph(paths, {spec}, status_db); - std::unordered_set<package_spec> key_set = Maps::extract_key_set(dependency_graph.adjacency_list()); - key_set.erase(spec); - return key_set; + const std::vector<package_spec> pkgs = graph.find_topological_sort(); + for (const package_spec& pkg : pkgs) + { + ret.push_back(package_spec_with_remove_plan(pkg, std::move(was_examined[pkg]))); + } + return ret; } -}} +} diff --git a/toolsrc/src/vcpkg_Environment.cpp b/toolsrc/src/vcpkg_Environment.cpp index d98b0f220..1babdc547 100644 --- a/toolsrc/src/vcpkg_Environment.cpp +++ b/toolsrc/src/vcpkg_Environment.cpp @@ -1,19 +1,19 @@ -#include <regex> -#include <array> +#include "pch.h" #include "vcpkg_Environment.h" #include "vcpkg_Commands.h" -#include "vcpkg.h" #include "metrics.h" #include "vcpkg_System.h" +#include "vcpkg_Strings.h" +#include "vcpkg_Files.h" -namespace vcpkg {namespace Environment +namespace vcpkg::Environment { static const fs::path default_cmake_installation_dir = "C:/Program Files/CMake/bin"; static const fs::path default_cmake_installation_dir_x86 = "C:/Program Files (x86)/CMake/bin"; static const fs::path default_git_installation_dir = "C:/Program Files/git/cmd"; static const fs::path default_git_installation_dir_x86 = "C:/Program Files (x86)/git/cmd"; - static void ensure_on_path(const std::array<int, 3>& version, const wchar_t* version_check_cmd, const wchar_t* install_cmd) + static void ensure_on_path(const std::array<int, 3>& version, const std::wstring& version_check_cmd, const std::wstring& install_cmd) { System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(version_check_cmd); if (ec_data.exit_code == 0) @@ -45,6 +45,13 @@ namespace vcpkg {namespace Environment } } + static std::wstring create_default_install_cmd(const vcpkg_paths& paths, const std::wstring& tool_name) + { + const fs::path script = paths.scripts / "fetchDependency.ps1"; + // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned + return Strings::wformat(L"powershell -ExecutionPolicy Bypass %s -Dependency %s", script.native(), tool_name); + } + void ensure_git_on_path(const vcpkg_paths& paths) { const fs::path downloaded_git = paths.downloads / "PortableGit" / "cmd"; @@ -56,13 +63,14 @@ namespace vcpkg {namespace Environment _wputenv_s(L"PATH", path_buf.c_str()); static constexpr std::array<int, 3> git_version = {2,0,0}; - // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned - ensure_on_path(git_version, L"git --version 2>&1", L"powershell -ExecutionPolicy Bypass scripts\\fetchDependency.ps1 -Dependency git"); + static const std::wstring version_check_cmd = L"git --version 2>&1"; + const std::wstring install_cmd = create_default_install_cmd(paths, L"git"); + ensure_on_path(git_version, version_check_cmd, install_cmd); } void ensure_cmake_on_path(const vcpkg_paths& paths) { - const fs::path downloaded_cmake = paths.downloads / "cmake-3.5.2-win32-x86" / "bin"; + const fs::path downloaded_cmake = paths.downloads / "cmake-3.7.2-win32-x86" / "bin"; const std::wstring path_buf = Strings::wformat(L"%s;%s;%s;%s", downloaded_cmake.native(), System::wdupenv_str(L"PATH"), @@ -70,18 +78,131 @@ namespace vcpkg {namespace Environment default_cmake_installation_dir_x86.native()); _wputenv_s(L"PATH", path_buf.c_str()); - static constexpr std::array<int, 3> cmake_version = {3,5,0}; - // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned - ensure_on_path(cmake_version, L"cmake --version 2>&1", L"powershell -ExecutionPolicy Bypass scripts\\fetchDependency.ps1 -Dependency cmake"); + static constexpr std::array<int, 3> cmake_version = {3,7,2}; + static const std::wstring version_check_cmd = L"cmake --version 2>&1"; + const std::wstring install_cmd = create_default_install_cmd(paths, L"cmake"); + ensure_on_path(cmake_version, version_check_cmd, install_cmd); } void ensure_nuget_on_path(const vcpkg_paths& paths) { - const std::wstring path_buf = Strings::wformat(L"%s;%s", paths.downloads.native(), System::wdupenv_str(L"PATH")); + const fs::path downloaded_nuget = paths.downloads / "nuget-3.5.0"; + const std::wstring path_buf = Strings::wformat(L"%s;%s", downloaded_nuget.native(), System::wdupenv_str(L"PATH")); _wputenv_s(L"PATH", path_buf.c_str()); - static constexpr std::array<int, 3> nuget_version = {1,0,0}; - // TODO: switch out ExecutionPolicy Bypass with "Remove Mark Of The Web" code and restore RemoteSigned - ensure_on_path(nuget_version, L"nuget 2>&1", L"powershell -ExecutionPolicy Bypass scripts\\fetchDependency.ps1 -Dependency nuget"); + static constexpr std::array<int, 3> nuget_version = {3,3,0}; + static const std::wstring version_check_cmd = L"nuget 2>&1"; + const std::wstring install_cmd = create_default_install_cmd(paths, L"nuget"); + ensure_on_path(nuget_version, version_check_cmd, install_cmd); + } + + static std::vector<std::string> get_VS2017_installation_instances(const vcpkg_paths& paths) + { + const fs::path script = paths.scripts / "findVisualStudioInstallationInstances.ps1"; + const std::wstring cmd = Strings::wformat(L"powershell -ExecutionPolicy Bypass %s", script.native()); + System::exit_code_and_output ec_data = System::cmd_execute_and_capture_output(cmd); + Checks::check_exit(ec_data.exit_code == 0, "Could not run script to detect VS 2017 instances"); + return Strings::split(ec_data.output, "\n"); + } + + static const fs::path& get_VS2015_installation_instance() + { + static const fs::path vs2015_cmntools = fs::path(System::wdupenv_str(L"VS140COMNTOOLS")).parent_path(); // The call to parent_path() is needed because the env variable has a trailing backslash + static const fs::path vs2015_path = vs2015_cmntools.parent_path().parent_path(); + return vs2015_path; + } + + static fs::path find_dumpbin_exe(const vcpkg_paths& paths) + { + const std::vector<std::string> vs2017_installation_instances = get_VS2017_installation_instances(paths); + std::vector<fs::path> paths_examined; + + // VS2017 + for (const std::string& instance : vs2017_installation_instances) + { + const fs::path msvc_path = Strings::format(R"(%s\VC\Tools\MSVC)", instance); + std::vector<fs::path> msvc_subdirectories; + Files::non_recursive_find_matching_paths_in_dir(msvc_path, [&](const fs::path& current) + { + return fs::is_directory(current); + }, &msvc_subdirectories); + + // 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; + } + } + } + + // VS2015 + const fs::path vs2015_dumpbin_exe = get_VS2015_installation_instance() / "VC" / "bin" / "dumpbin.exe"; + paths_examined.push_back(vs2015_dumpbin_exe); + if (fs::exists(vs2015_dumpbin_exe)) + { + return vs2015_dumpbin_exe; + } + + System::println(System::color::error, "Could not detect dumpbin.exe."); + System::println("The following paths were examined:"); + for (const fs::path& path : paths_examined) + { + System::println(" %s", path.generic_string()); + } + exit(EXIT_FAILURE); + } + + const fs::path& get_dumpbin_exe(const vcpkg_paths& paths) + { + static const fs::path dumpbin_exe = find_dumpbin_exe(paths); + return dumpbin_exe; + } + + static vcvarsall_and_platform_toolset find_vcvarsall_bat(const vcpkg_paths& paths) + { + const std::vector<std::string> vs2017_installation_instances = get_VS2017_installation_instances(paths); + std::vector<fs::path> paths_examined; + + // VS2017 + for (const fs::path& instance : vs2017_installation_instances) + { + const fs::path vcvarsall_bat = instance / "VC" / "Auxiliary" / "Build" / "vcvarsall.bat"; + paths_examined.push_back(vcvarsall_bat); + if (fs::exists(vcvarsall_bat)) + { + return { vcvarsall_bat , L"v141"}; + } + } + + // VS2015 + const fs::path vs2015_vcvarsall_bat = get_VS2015_installation_instance() / "VC" / "vcvarsall.bat"; + paths_examined.push_back(vs2015_vcvarsall_bat); + if (fs::exists(vs2015_vcvarsall_bat)) + { + return { vs2015_vcvarsall_bat, L"v140" }; + } + + System::println(System::color::error, "Could not detect vccarsall.bat."); + System::println("The following paths were examined:"); + for (const fs::path& path : paths_examined) + { + System::println(" %s",path.generic_string()); + } + exit(EXIT_FAILURE); + } + + const vcvarsall_and_platform_toolset& get_vcvarsall_bat(const vcpkg_paths& paths) + { + static const vcvarsall_and_platform_toolset vcvarsall_bat = find_vcvarsall_bat(paths); + return vcvarsall_bat; } -}} +} diff --git a/toolsrc/src/vcpkg_Files.cpp b/toolsrc/src/vcpkg_Files.cpp index 611aa7450..87700238d 100644 --- a/toolsrc/src/vcpkg_Files.cpp +++ b/toolsrc/src/vcpkg_Files.cpp @@ -1,25 +1,22 @@ +#include "pch.h" #include "vcpkg_Files.h" -#include <fstream> -#include <filesystem> -#include <regex> +#include "vcpkg_System.h" -namespace fs = std::tr2::sys; - -namespace vcpkg {namespace Files +namespace vcpkg::Files { static const std::regex FILESYSTEM_INVALID_CHARACTERS_REGEX = std::regex(R"([\/:*?"<>|])"); void check_is_directory(const fs::path& dirpath) { - Checks::check_throw(fs::is_directory(dirpath), "The path %s is not a directory", dirpath.string()); + Checks::check_exit(fs::is_directory(dirpath), "The path %s is not a directory", dirpath.string()); } - bool has_invalid_chars_for_filesystem(const std::string s) + bool has_invalid_chars_for_filesystem(const std::string& s) { return std::regex_search(s, FILESYSTEM_INVALID_CHARACTERS_REGEX); } - expected<std::string> get_contents(const fs::path& file_path) noexcept + expected<std::string> read_contents(const fs::path& file_path) noexcept { std::fstream file_stream(file_path, std::ios_base::in | std::ios_base::binary); if (file_stream.fail()) @@ -44,6 +41,35 @@ namespace vcpkg {namespace Files return std::move(output); } + expected<std::vector<std::string>> read_all_lines(const fs::path& file_path) + { + 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; + } + + std::vector<std::string> output; + std::string line; + while (std::getline(file_stream, line)) + { + output.push_back(line); + } + file_stream.close(); + + return std::move(output); + } + + void write_all_lines(const fs::path& file_path, const std::vector<std::string>& lines) + { + std::fstream output(file_path, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + for (const std::string& line : lines) + { + output << line << "\n"; + } + output.close(); + } + fs::path find_file_recursively_up(const fs::path& starting_dir, const std::string& filename) { fs::path current_dir = starting_dir; @@ -58,4 +84,59 @@ namespace vcpkg {namespace Files return current_dir; } -}} + + void recursive_find_files_with_extension_in_dir(const fs::path& dir, const std::string& extension, std::vector<fs::path>* output) + { + recursive_find_matching_paths_in_dir(dir, [&extension](const fs::path& current) + { + return !fs::is_directory(current) && current.extension() == extension; + }, output); + } + + std::vector<fs::path> recursive_find_files_with_extension_in_dir(const fs::path& dir, const std::string& extension) + { + std::vector<fs::path> v; + recursive_find_files_with_extension_in_dir(dir, extension, &v); + return v; + } + + void recursive_find_all_files_in_dir(const fs::path& dir, std::vector<fs::path>* output) + { + recursive_find_matching_paths_in_dir(dir, [&](const fs::path& current) + { + return !fs::is_directory(current); + }, output); + } + + std::vector<fs::path> recursive_find_all_files_in_dir(const fs::path& dir) + { + std::vector<fs::path> v; + recursive_find_all_files_in_dir(dir, &v); + return v; + } + + void non_recursive_find_all_files_in_dir(const fs::path& dir, std::vector<fs::path>* output) + { + non_recursive_find_matching_paths_in_dir(dir, [&](const fs::path& current) + { + return !fs::is_directory(current); + }, output); + } + + std::vector<fs::path> non_recursive_find_all_files_in_dir(const fs::path& dir) + { + std::vector<fs::path> v; + non_recursive_find_all_files_in_dir(dir, &v); + return v; + } + + void print_paths(const std::vector<fs::path>& paths) + { + System::println(""); + for (const fs::path& p : paths) + { + System::println(" %s", p.generic_string()); + } + System::println(""); + } +} diff --git a/toolsrc/src/vcpkg_Input.cpp b/toolsrc/src/vcpkg_Input.cpp index f7aae1929..5720cadc0 100644 --- a/toolsrc/src/vcpkg_Input.cpp +++ b/toolsrc/src/vcpkg_Input.cpp @@ -1,11 +1,12 @@ +#include "pch.h" #include "vcpkg_Input.h" #include "vcpkg_System.h" #include "metrics.h" #include "vcpkg_Commands.h" -namespace vcpkg {namespace Input +namespace vcpkg::Input { - package_spec check_and_get_package_spec(const std::string& package_spec_as_string, const triplet& default_target_triplet, const char* example_text) + package_spec check_and_get_package_spec(const std::string& package_spec_as_string, const triplet& default_target_triplet, const std::string& example_text) { const std::string as_lowercase = Strings::ascii_to_lowercase(package_spec_as_string); expected<package_spec> expected_spec = package_spec::from_string(as_lowercase, default_target_triplet); @@ -20,7 +21,7 @@ namespace vcpkg {namespace Input exit(EXIT_FAILURE); } - std::vector<package_spec> check_and_get_package_specs(const std::vector<std::string>& package_specs_as_strings, const triplet& default_target_triplet, const char* example_text) + std::vector<package_spec> check_and_get_package_specs(const std::vector<std::string>& package_specs_as_strings, const triplet& default_target_triplet, const std::string& example_text) { std::vector<package_spec> specs; for (const std::string& spec : package_specs_as_strings) @@ -37,16 +38,16 @@ namespace vcpkg {namespace Input { System::println(System::color::error, "Error: invalid triplet: %s", t.canonical_name()); TrackProperty("error", "invalid triplet: " + t.canonical_name()); - help_topic_valid_triplet(paths); + Commands::Help::help_topic_valid_triplet(paths); exit(EXIT_FAILURE); } } - void check_triplets(std::vector<package_spec> triplets, const vcpkg_paths& paths) + void check_triplets(const std::vector<package_spec>& triplets, const vcpkg_paths& paths) { for (const package_spec& spec : triplets) { check_triplet(spec.target_triplet(), paths); } } -}} +} diff --git a/toolsrc/src/vcpkg_Strings.cpp b/toolsrc/src/vcpkg_Strings.cpp index 56eeae7a0..3b9d6a859 100644 --- a/toolsrc/src/vcpkg_Strings.cpp +++ b/toolsrc/src/vcpkg_Strings.cpp @@ -1,12 +1,14 @@ +#include "pch.h" #include "vcpkg_Strings.h" -#include <cstdarg> -#include <algorithm> -#include <codecvt> -#include <iterator> - -namespace vcpkg {namespace Strings {namespace details +namespace vcpkg::Strings::details { + // To disambiguate between two overloads + static const auto isspace = [](const char c) + { + return std::isspace(c); + }; + std::string format_internal(const char* fmtstr, ...) { va_list lst; @@ -32,9 +34,9 @@ namespace vcpkg {namespace Strings {namespace details return output; } -}}} +} -namespace vcpkg {namespace Strings +namespace vcpkg::Strings { std::wstring utf8_to_utf16(const std::string& s) { @@ -64,4 +66,84 @@ namespace vcpkg {namespace Strings std::transform(output.begin(), output.end(), output.begin(), ::tolower); return output; } -}} + + std::string join(const std::vector<std::string>& v, const std::string& prefix, const std::string& delimiter, const std::string& suffix) + { + return join(v, prefix, delimiter, suffix, [](const std::string& i) -> std::string + { + return i; + }); + } + + Joiner Joiner::on(const std::string& delimiter) + { + return Joiner(delimiter); + } + + Joiner& Joiner::prefix(const std::string& prefix) + { + this->m_prefix = prefix; + return *this; + } + + Joiner& Joiner::suffix(const std::string& suffix) + { + this->m_suffix = suffix; + return *this; + } + + std::string Joiner::join(const std::vector<std::string>& v) const + { + return Strings::join(v, this->m_prefix, this->m_delimiter, this->m_suffix); + } + + Joiner::Joiner(const std::string& delimiter) : m_prefix(""), m_delimiter(delimiter), m_suffix("") + { + } + + 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); + } + + strings->erase(std::remove_if(strings->begin(), strings->end(), [](const std::string& s)-> bool + { + return s == ""; + }), strings->end()); + } + + 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 index cc7080069..754a26741 100644 --- a/toolsrc/src/vcpkg_System.cpp +++ b/toolsrc/src/vcpkg_System.cpp @@ -1,11 +1,7 @@ +#include "pch.h" #include "vcpkg_System.h" -#include <iostream> -#include <Windows.h> -#include <regex> -namespace fs = std::tr2::sys; - -namespace vcpkg {namespace System +namespace vcpkg::System { fs::path get_exe_path_of_current_process() { @@ -58,7 +54,7 @@ namespace vcpkg {namespace System std::cout << "\n"; } - void print(color c, const char* message) + void print(const color c, const char* message) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); @@ -71,7 +67,7 @@ namespace vcpkg {namespace System SetConsoleTextAttribute(hConsole, original_color); } - void println(color c, const char* message) + void println(const color c, const char* message) { print(c, message); std::cout << "\n"; @@ -106,6 +102,6 @@ namespace vcpkg {namespace System double Stopwatch2::microseconds() const { return (reinterpret_cast<const LARGE_INTEGER*>(&end_time)->QuadPart - - reinterpret_cast<const LARGE_INTEGER*>(&start_time)->QuadPart) * 1000000.0 / reinterpret_cast<const LARGE_INTEGER*>(&freq)->QuadPart; + reinterpret_cast<const LARGE_INTEGER*>(&start_time)->QuadPart) * 1000000.0 / reinterpret_cast<const LARGE_INTEGER*>(&freq)->QuadPart; } -}} +} diff --git a/toolsrc/src/vcpkg_cmd_arguments.cpp b/toolsrc/src/vcpkg_cmd_arguments.cpp index a286ba9b7..fdeb6e877 100644 --- a/toolsrc/src/vcpkg_cmd_arguments.cpp +++ b/toolsrc/src/vcpkg_cmd_arguments.cpp @@ -1,11 +1,7 @@ -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> +#include "pch.h" #include "vcpkg_cmd_arguments.h" #include "vcpkg_Commands.h" -#include "vcpkg_Graphs.h" -#include <unordered_set> #include "metrics.h" -#include "vcpkg.h" #include "vcpkg_System.h" namespace vcpkg @@ -20,7 +16,7 @@ namespace vcpkg { System::println(System::color::error, "Error: expected value after %s", option_name); TrackProperty("error", "error option name"); - print_usage(); + Commands::Help::print_usage(); exit(EXIT_FAILURE); } @@ -28,7 +24,7 @@ namespace vcpkg { System::println(System::color::error, "Error: %s specified multiple times", option_name); TrackProperty("error", "error option specified multiple times"); - print_usage(); + Commands::Help::print_usage(); exit(EXIT_FAILURE); } @@ -36,15 +32,15 @@ namespace vcpkg } static void parse_switch( - opt_bool new_setting, + opt_bool_t new_setting, const std::string& option_name, - opt_bool& option_field) + opt_bool_t& option_field) { - if (option_field != opt_bool::unspecified && option_field != new_setting) + if (option_field != opt_bool_t::UNSPECIFIED && option_field != new_setting) { System::println(System::color::error, "Error: conflicting values specified for --%s", option_name); TrackProperty("error", "error conflicting switches"); - print_usage(); + Commands::Help::print_usage(); exit(EXIT_FAILURE); } option_field = new_setting; @@ -98,27 +94,27 @@ namespace vcpkg } if (arg == "--debug") { - parse_switch(opt_bool::enabled, "debug", args.debug); + parse_switch(opt_bool_t::ENABLED, "debug", args.debug); continue; } if (arg == "--sendmetrics") { - parse_switch(opt_bool::enabled, "sendmetrics", args.sendmetrics); + parse_switch(opt_bool_t::ENABLED, "sendmetrics", args.sendmetrics); continue; } if (arg == "--printmetrics") { - parse_switch(opt_bool::enabled, "printmetrics", args.printmetrics); + parse_switch(opt_bool_t::ENABLED, "printmetrics", args.printmetrics); continue; } if (arg == "--no-sendmetrics") { - parse_switch(opt_bool::disabled, "sendmetrics", args.sendmetrics); + parse_switch(opt_bool_t::DISABLED, "sendmetrics", args.sendmetrics); continue; } if (arg == "--no-printmetrics") { - parse_switch(opt_bool::disabled, "printmetrics", args.printmetrics); + parse_switch(opt_bool_t::DISABLED, "printmetrics", args.printmetrics); continue; } @@ -158,7 +154,7 @@ namespace vcpkg System::println(System::color::error, "Unknown option(s) for command '%s':", this->command); for (const std::string& option : options_copy) { - System::println(option.c_str()); + System::println(option); } exit(EXIT_FAILURE); } @@ -181,7 +177,7 @@ namespace vcpkg return check_exact_arg_count(expected_arg_count, ""); } - void vcpkg_cmd_arguments::check_max_arg_count(const size_t expected_arg_count, const char* example_text) const + void vcpkg_cmd_arguments::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) @@ -192,7 +188,7 @@ namespace vcpkg } } - void vcpkg_cmd_arguments::check_min_arg_count(const size_t expected_arg_count, const char* example_text) const + void vcpkg_cmd_arguments::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) @@ -203,7 +199,7 @@ namespace vcpkg } } - void vcpkg_cmd_arguments::check_exact_arg_count(const size_t expected_arg_count, const char* example_text) const + void vcpkg_cmd_arguments::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) diff --git a/toolsrc/src/vcpkg_info.cpp b/toolsrc/src/vcpkg_info.cpp new file mode 100644 index 000000000..f8e214998 --- /dev/null +++ b/toolsrc/src/vcpkg_info.cpp @@ -0,0 +1,35 @@ +#include "pch.h" +#include "vcpkg_info.h" +#include "metrics.h" + +#define STRINGIFY(X) #X +#define MACRO_TO_STRING(X) STRINGIFY(X) + +#define VCPKG_VERSION_AS_STRING MACRO_TO_STRING(VCPKG_VERSION)"" // Double quotes needed at the end to prevent blank token + +namespace vcpkg::Info +{ + const std::string& version() + { + static const std::string s_version = +#include "../VERSION.txt" + + +#pragma warning( push ) +#pragma warning( disable : 4003) + // VCPKG_VERSION can be defined but have no value, which yields C4003. + + std::string(VCPKG_VERSION_AS_STRING) +#pragma warning( pop ) +#ifndef NDEBUG + + std::string("-debug") +#endif + + std::string(GetCompiledMetricsEnabled() ? "" : "-external"); + return s_version; + } + + const std::string& email() + { + static const std::string s_email = R"(vcpkg@microsoft.com)"; + return s_email; + } +} diff --git a/toolsrc/src/vcpkg_metrics_uploader.cpp b/toolsrc/src/vcpkg_metrics_uploader.cpp index f1f4a52ed..14fc9ae48 100644 --- a/toolsrc/src/vcpkg_metrics_uploader.cpp +++ b/toolsrc/src/vcpkg_metrics_uploader.cpp @@ -1,10 +1,8 @@ #include "metrics.h" -#include <filesystem> #include "vcpkg_Checks.h" #include "vcpkg_Files.h" #include <Windows.h> -namespace fs = std::tr2::sys; using namespace vcpkg; int WINAPI @@ -21,5 +19,5 @@ WinMain( szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount); Checks::check_exit(argCount == 2, "Requires exactly one argument, the path to the payload file"); - Upload(Files::get_contents(szArgList[1]).get_or_throw()); + Upload(Files::read_contents(szArgList[1]).get_or_throw()); } diff --git a/toolsrc/src/vcpkg_paths.cpp b/toolsrc/src/vcpkg_paths.cpp index 1f9eb0bc5..8d7060a02 100644 --- a/toolsrc/src/vcpkg_paths.cpp +++ b/toolsrc/src/vcpkg_paths.cpp @@ -1,4 +1,4 @@ -#include <filesystem> +#include "pch.h" #include "expected.h" #include "vcpkg_paths.h" #include "metrics.h" @@ -32,8 +32,9 @@ namespace vcpkg paths.ports = paths.root / "ports"; paths.installed = paths.root / "installed"; paths.triplets = paths.root / "triplets"; + paths.scripts = paths.root / "scripts"; - paths.buildsystems = paths.root / "scripts" / "buildsystems"; + paths.buildsystems = paths.scripts / "buildsystems"; paths.buildsystems_msbuild_targets = paths.buildsystems / "msbuild" / "vcpkg.targets"; paths.vcpkg_dir = paths.installed / "vcpkg"; @@ -41,7 +42,7 @@ namespace vcpkg paths.vcpkg_dir_info = paths.vcpkg_dir / "info"; paths.vcpkg_dir_updates = paths.vcpkg_dir / "updates"; - paths.ports_cmake = paths.root / "scripts" / "ports.cmake"; + paths.ports_cmake = paths.scripts / "ports.cmake"; return paths; } @@ -55,6 +56,16 @@ namespace vcpkg return this->ports / spec.name(); } + fs::path vcpkg_paths::build_info_file_path(const package_spec& spec) const + { + return this->package_dir(spec) / "BUILD_INFO"; + } + + fs::path vcpkg_paths::listfile_path(const BinaryParagraph& pgh) const + { + return this->vcpkg_dir_info / (pgh.fullstem() + ".list"); + } + bool vcpkg_paths::is_valid_triplet(const triplet& t) const { auto it = fs::directory_iterator(this->triplets); diff --git a/toolsrc/src/vcpkg_version.cpp b/toolsrc/src/vcpkg_version.cpp deleted file mode 100644 index da52b7cab..000000000 --- a/toolsrc/src/vcpkg_version.cpp +++ /dev/null @@ -1,25 +0,0 @@ -#include "vcpkg.h" -#include "metrics.h" - -#define STRINGIFY(X) #X -#define MACRO_TO_STRING(X) STRINGIFY(X) - -#define VCPKG_VERSION_AS_STRING MACRO_TO_STRING(VCPKG_VERSION)"" // Double quotes needed at the end to prevent blank token - -const std::string& vcpkg::version() -{ - static const std::string s_version = -#include "../VERSION.txt" - - -#pragma warning( push ) -#pragma warning( disable : 4003) - // VCPKG_VERSION can be defined but have no value, which yields C4003. - + std::string(VCPKG_VERSION_AS_STRING) -#pragma warning( pop ) -#ifndef NDEBUG - + std::string("-debug") -#endif - + std::string(GetCompiledMetricsEnabled() ? "" : "-external"); - return s_version; -} diff --git a/toolsrc/src/vcpkglib.cpp b/toolsrc/src/vcpkglib.cpp new file mode 100644 index 000000000..06487684c --- /dev/null +++ b/toolsrc/src/vcpkglib.cpp @@ -0,0 +1,240 @@ +#include "pch.h" +#include "vcpkglib.h" +#include "vcpkg_Files.h" +#include "Paragraphs.h" +#include "metrics.h" + +using namespace vcpkg; + +static StatusParagraphs load_current_database(const fs::path& vcpkg_dir_status_file, const fs::path& vcpkg_dir_status_file_old) +{ + if (!fs::exists(vcpkg_dir_status_file)) + { + if (!fs::exists(vcpkg_dir_status_file_old)) + { + // no status file, use empty db + return StatusParagraphs(); + } + + fs::rename(vcpkg_dir_status_file_old, vcpkg_dir_status_file); + } + + auto text = Files::read_contents(vcpkg_dir_status_file).get_or_throw(); + auto pghs = Paragraphs::parse_paragraphs(text); + + std::vector<std::unique_ptr<StatusParagraph>> status_pghs; + for (auto&& p : pghs) + { + status_pghs.push_back(std::make_unique<StatusParagraph>(p)); + } + + return StatusParagraphs(std::move(status_pghs)); +} + +StatusParagraphs vcpkg::database_load_check(const vcpkg_paths& paths) +{ + auto updates_dir = paths.vcpkg_dir_updates; + + std::error_code ec; + fs::create_directory(paths.installed, ec); + fs::create_directory(paths.vcpkg_dir, ec); + fs::create_directory(paths.vcpkg_dir_info, ec); + fs::create_directory(updates_dir, ec); + + const fs::path& status_file = paths.vcpkg_dir_status_file; + const fs::path status_file_old = status_file.parent_path() / "status-old"; + const fs::path status_file_new = status_file.parent_path() / "status-new"; + + StatusParagraphs current_status_db = load_current_database(status_file, status_file_old); + + auto b = fs::directory_iterator(updates_dir); + auto e = fs::directory_iterator(); + if (b == e) + { + // updates directory is empty, control file is up-to-date. + return current_status_db; + } + + for (; b != e; ++b) + { + if (!fs::is_regular_file(b->status())) + continue; + if (b->path().filename() == "incomplete") + continue; + + auto text = Files::read_contents(b->path()).get_or_throw(); + auto pghs = Paragraphs::parse_paragraphs(text); + for (auto&& p : pghs) + { + current_status_db.insert(std::make_unique<StatusParagraph>(p)); + } + } + + std::fstream(status_file_new, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc) << current_status_db; + + if (fs::exists(status_file_old)) + fs::remove(status_file_old); + if (fs::exists(status_file)) + fs::rename(status_file, status_file_old); + fs::rename(status_file_new, status_file); + fs::remove(status_file_old); + + b = fs::directory_iterator(updates_dir); + for (; b != e; ++b) + { + if (!fs::is_regular_file(b->status())) + continue; + fs::remove(b->path()); + } + + return current_status_db; +} + +void vcpkg::write_update(const vcpkg_paths& paths, const StatusParagraph& p) +{ + static int update_id = 0; + auto my_update_id = update_id++; + auto tmp_update_filename = paths.vcpkg_dir_updates / "incomplete"; + auto update_filename = paths.vcpkg_dir_updates / std::to_string(my_update_id); + std::fstream fs(tmp_update_filename, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + fs << p; + fs.close(); + fs::rename(tmp_update_filename, update_filename); +} + +static void upgrade_to_slash_terminated_sorted_format(std::vector<std::string>* lines, const fs::path& listfile_path) +{ + static bool was_tracked = false; + + if (lines->empty()) + { + return; + } + + if (lines->at(0).back() == '/') + { + return; // File already in the new format + } + + if (!was_tracked) + { + was_tracked = true; + TrackProperty("listfile", "update to new format"); + } + + // The files are sorted such that directories are placed just before the files they contain + // (They are not necessarily sorted alphabetically, e.g. libflac) + // Therefore we can detect the entries that represent directories by comparing every element with the next one + // and checking if the next has a slash immediately after the current one's length + for (size_t i = 0; i < lines->size() - 1; i++) + { + std::string& current_string = lines->at(i); + const std::string& next_string = lines->at(i + 1); + + const size_t potential_slash_char_index = current_string.length(); + // Make sure the index exists first + if (next_string.size() > potential_slash_char_index && next_string.at(potential_slash_char_index) == '/') + { + current_string += '/'; // Mark as a directory + } + } + + // After suffixing the directories with a slash, we can now sort. + // We cannot sort before adding the suffixes because the following (actual example): + /* + x86-windows/include/FLAC <<<<<< This would be separated from its group due to sorting + x86-windows/include/FLAC/all.h + x86-windows/include/FLAC/assert.h + x86-windows/include/FLAC/callback.h + x86-windows/include/FLAC++ + x86-windows/include/FLAC++/all.h + x86-windows/include/FLAC++/decoder.h + x86-windows/include/FLAC++/encoder.h + * + x86-windows/include/FLAC/ <<<<<< This will now be kept with its group when sorting + x86-windows/include/FLAC/all.h + x86-windows/include/FLAC/assert.h + x86-windows/include/FLAC/callback.h + x86-windows/include/FLAC++/ + x86-windows/include/FLAC++/all.h + x86-windows/include/FLAC++/decoder.h + x86-windows/include/FLAC++/encoder.h + */ + // Note that after sorting, the FLAC++/ group will be placed before the FLAC/ group + // The new format is lexicographically sorted + std::sort(lines->begin(), lines->end()); + +#if 0 + // Replace the listfile on disk + const fs::path updated_listfile_path = listfile_path.generic_string() + "_updated"; + Files::write_all_lines(updated_listfile_path, *lines); + fs::rename(updated_listfile_path, listfile_path); +#endif +} + +std::vector<StatusParagraph_and_associated_files> vcpkg::get_installed_files(const vcpkg_paths& paths, const StatusParagraphs& status_db) +{ + std::vector<StatusParagraph_and_associated_files> installed_files; + + for (const std::unique_ptr<StatusParagraph>& pgh : status_db) + { + if (pgh->state != install_state_t::installed) + { + continue; + } + + const fs::path listfile_path = paths.listfile_path(pgh->package); + std::vector<std::string> installed_files_of_current_pgh = Files::read_all_lines(listfile_path).get_or_throw(); + Strings::trim_all_and_remove_whitespace_strings(&installed_files_of_current_pgh); + upgrade_to_slash_terminated_sorted_format(&installed_files_of_current_pgh, listfile_path); + + // Remove the directories + installed_files_of_current_pgh.erase( + std::remove_if(installed_files_of_current_pgh.begin(), installed_files_of_current_pgh.end(), [](const std::string& file) -> bool + { + return file.back() == '/'; + } + ), installed_files_of_current_pgh.end()); + + StatusParagraph_and_associated_files pgh_and_files = {*pgh, ImmutableSortedVector<std::string>::create(std::move(installed_files_of_current_pgh))}; + installed_files.push_back(std::move(pgh_and_files)); + } + + return installed_files; +} + +expected<SourceParagraph> vcpkg::try_load_port(const fs::path& path) +{ + try + { + auto pghs = Paragraphs::get_paragraphs(path / "CONTROL"); + Checks::check_exit(pghs.size() == 1, "Invalid control file at %s\\CONTROL", path.string()); + return SourceParagraph(pghs[0]); + } + catch (std::runtime_error const&) + { + } + + return std::errc::no_such_file_or_directory; +} + +expected<BinaryParagraph> vcpkg::try_load_cached_package(const vcpkg_paths& paths, const package_spec& spec) +{ + const fs::path path = paths.package_dir(spec) / "CONTROL"; + + auto control_contents_maybe = Files::read_contents(path); + if (auto control_contents = control_contents_maybe.get()) + { + std::vector<std::unordered_map<std::string, std::string>> pghs; + try + { + pghs = Paragraphs::parse_paragraphs(*control_contents); + } + catch (std::runtime_error) + { + } + Checks::check_exit(pghs.size() == 1, "Invalid control file at %s", path.string()); + return BinaryParagraph(pghs[0]); + } + return control_contents_maybe.error_code(); +} diff --git a/toolsrc/src/vcpkglib_helpers.cpp b/toolsrc/src/vcpkglib_helpers.cpp index 3aa3735b0..6b96c25cb 100644 --- a/toolsrc/src/vcpkglib_helpers.cpp +++ b/toolsrc/src/vcpkglib_helpers.cpp @@ -1,8 +1,8 @@ +#include "pch.h" #include "vcpkg_Checks.h" #include "vcpkglib_helpers.h" -#include <unordered_map> -namespace vcpkg {namespace details +namespace vcpkg::details { std::string optional_field(const std::unordered_map<std::string, std::string>& fields, const std::string& fieldname) { @@ -13,41 +13,43 @@ namespace vcpkg {namespace details } 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); - vcpkg::Checks::check_throw(it != fields.end(), "Required field not present: %s", fieldname); + Checks::check_exit(it != fields.end(), "Required field not present: %s", fieldname); return it->second; - }; + } - std::vector<std::string> parse_depends(const std::string& depends_string) + std::string remove_required_field(std::unordered_map<std::string, std::string>* fields, const std::string& fieldname) { - std::vector<std::string> out; + auto it = fields->find(fieldname); + Checks::check_exit(it != fields->end(), "Required field not present: %s", fieldname); - 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); + const std::string value = std::move(it->second); + fields->erase(it); + return value; + } - return out; + std::string shorten_description(const std::string& desc) + { + auto simple_desc = std::regex_replace(desc.substr(0, 49), std::regex("\\n( |\\t)?"), ""); + if (desc.size() > 49) + simple_desc.append("..."); + return simple_desc; } -}} +} |
