diff options
Diffstat (limited to 'toolsrc/src')
| -rw-r--r-- | toolsrc/src/vcpkg/portfileprovider.cpp | 190 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/registries.cpp | 83 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/vcpkgpaths.cpp | 193 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/versiondeserializers.cpp | 210 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/versions.cpp | 34 |
5 files changed, 638 insertions, 72 deletions
diff --git a/toolsrc/src/vcpkg/portfileprovider.cpp b/toolsrc/src/vcpkg/portfileprovider.cpp index 9c09a23e4..e3053bfa6 100644 --- a/toolsrc/src/vcpkg/portfileprovider.cpp +++ b/toolsrc/src/vcpkg/portfileprovider.cpp @@ -1,3 +1,6 @@ + + +#include <vcpkg/base/json.h> #include <vcpkg/base/system.debug.h> #include <vcpkg/configuration.h> @@ -7,6 +10,33 @@ #include <vcpkg/sourceparagraph.h> #include <vcpkg/vcpkgcmdarguments.h> #include <vcpkg/vcpkgpaths.h> +#include <vcpkg/versiondeserializers.h> + +#include <regex> + +using namespace vcpkg; +using namespace Versions; + +namespace +{ + Optional<fs::path> get_versions_json_path(const VcpkgPaths& paths, StringView port_name) + { + const auto port_versions_dir_path = paths.root / fs::u8path("port_versions"); + const auto subpath = Strings::concat(port_name.substr(0, 1), "-/", port_name, ".json"); + const auto json_path = port_versions_dir_path / subpath; + if (paths.get_filesystem().exists(json_path)) + { + return json_path; + } + return nullopt; + } + + Optional<fs::path> get_baseline_json_path(const VcpkgPaths& paths, StringView baseline_commit_sha) + { + const auto baseline_json = paths.git_checkout_baseline(paths.get_filesystem(), baseline_commit_sha); + return paths.get_filesystem().exists(baseline_json) ? make_optional(baseline_json) : nullopt; + } +} namespace vcpkg::PortFileProvider { @@ -27,7 +57,7 @@ namespace vcpkg::PortFileProvider return Util::fmap(ports, [](auto&& kvpair) -> const SourceControlFileLocation* { return &kvpair.second; }); } - PathsPortFileProvider::PathsPortFileProvider(const vcpkg::VcpkgPaths& paths_, + PathsPortFileProvider::PathsPortFileProvider(const VcpkgPaths& paths_, const std::vector<std::string>& overlay_ports_) : paths(paths_) { @@ -80,7 +110,7 @@ namespace vcpkg::PortFileProvider } else { - vcpkg::print_error_message(maybe_scf.error()); + print_error_message(maybe_scf.error()); Checks::exit_with_message( VCPKG_LINE_INFO, "Error: Failed to load port %s from %s", spec, fs::u8string(ports_dir)); } @@ -106,7 +136,7 @@ namespace vcpkg::PortFileProvider } else { - vcpkg::print_error_message(found_scf.error()); + print_error_message(found_scf.error()); Checks::exit_with_message( VCPKG_LINE_INFO, "Error: Failed to load port %s from %s", spec, fs::u8string(ports_dir)); } @@ -148,7 +178,7 @@ namespace vcpkg::PortFileProvider } else { - vcpkg::print_error_message(found_scf.error()); + print_error_message(found_scf.error()); Checks::exit_with_message( VCPKG_LINE_INFO, "Error: Failed to load port %s from %s", spec, fs::u8string(port_directory)); } @@ -224,7 +254,7 @@ namespace vcpkg::PortFileProvider } else { - vcpkg::print_error_message(maybe_scf.error()); + print_error_message(maybe_scf.error()); Checks::exit_with_message( VCPKG_LINE_INFO, "Error: Failed to load port from %s", fs::u8string(ports_dir)); } @@ -257,4 +287,154 @@ namespace vcpkg::PortFileProvider return ret; } + + namespace details + { + struct BaselineProviderImpl + { + BaselineProviderImpl(const VcpkgPaths& paths, const std::string& baseline) + : paths(paths), baseline(baseline) + { + } + ~BaselineProviderImpl() { } + + const std::map<std::string, VersionT, std::less<>>& get_baseline_cache() const + { + return baseline_cache.get_lazy([&]() -> auto { + auto maybe_baseline_file = get_baseline_json_path(paths, baseline); + Checks::check_exit(VCPKG_LINE_INFO, maybe_baseline_file.has_value(), "Couldn't find baseline.json"); + auto baseline_file = maybe_baseline_file.value_or_exit(VCPKG_LINE_INFO); + + auto maybe_baselines_map = parse_baseline_file(paths.get_filesystem(), "default", baseline_file); + Checks::check_exit(VCPKG_LINE_INFO, + maybe_baselines_map.has_value(), + "Error: Couldn't parse baseline `%s` from `%s`", + "default", + fs::u8string(baseline_file)); + auto baselines_map = *maybe_baselines_map.get(); + return std::move(baselines_map); + }); + } + + private: + const VcpkgPaths& paths; + const std::string baseline; + Lazy<std::map<std::string, VersionT, std::less<>>> baseline_cache; + }; + + struct VersionedPortfileProviderImpl + { + std::map<std::string, std::vector<VersionSpec>> versions_cache; + std::unordered_map<VersionSpec, std::string, VersionSpecHasher> git_tree_cache; + std::unordered_map<VersionSpec, SourceControlFileLocation, VersionSpecHasher> control_cache; + + VersionedPortfileProviderImpl(const VcpkgPaths& paths) : paths(paths) { } + ~VersionedPortfileProviderImpl() { } + + const VcpkgPaths& get_paths() const { return paths; } + Files::Filesystem& get_filesystem() const { return paths.get_filesystem(); } + + private: + const VcpkgPaths& paths; + }; + } + + VersionedPortfileProvider::VersionedPortfileProvider(const VcpkgPaths& paths) + : m_impl(std::make_unique<details::VersionedPortfileProviderImpl>(paths)) + { + } + VersionedPortfileProvider::~VersionedPortfileProvider() { } + + const std::vector<VersionSpec>& VersionedPortfileProvider::get_port_versions(StringView port_name) const + { + auto cache_it = m_impl->versions_cache.find(port_name.to_string()); + if (cache_it != m_impl->versions_cache.end()) + { + return cache_it->second; + } + + auto maybe_versions_file_path = get_versions_json_path(m_impl->get_paths(), port_name); + Checks::check_exit(VCPKG_LINE_INFO, + maybe_versions_file_path.has_value(), + "Error: Couldn't find a versions database file: %s.json.", + port_name); + auto versions_file_path = maybe_versions_file_path.value_or_exit(VCPKG_LINE_INFO); + + auto maybe_version_entries = parse_versions_file(m_impl->get_filesystem(), port_name, versions_file_path); + Checks::check_exit(VCPKG_LINE_INFO, + maybe_version_entries.has_value(), + "Error: Couldn't parse versions from file: %s", + fs::u8string(versions_file_path)); + auto version_entries = maybe_version_entries.value_or_exit(VCPKG_LINE_INFO); + + auto port = port_name.to_string(); + for (auto&& version_entry : version_entries) + { + VersionSpec spec(port, version_entry.version, version_entry.scheme); + m_impl->versions_cache[port].push_back(spec); + m_impl->git_tree_cache.emplace(std::move(spec), std::move(version_entry.git_tree)); + } + return m_impl->versions_cache.at(port); + } + + ExpectedS<const SourceControlFileLocation&> VersionedPortfileProvider::get_control_file( + const VersionSpec& version_spec) const + { + auto cache_it = m_impl->control_cache.find(version_spec); + if (cache_it != m_impl->control_cache.end()) + { + return cache_it->second; + } + + // Pre-populate versions cache. + get_port_versions(version_spec.port_name); + + auto git_tree_cache_it = m_impl->git_tree_cache.find(version_spec); + if (git_tree_cache_it == m_impl->git_tree_cache.end()) + { + return Strings::concat("No git object SHA for entry %s at version %s.", + version_spec.port_name, + version_spec.version.to_string()); + } + + const std::string git_tree = git_tree_cache_it->second; + auto port_directory = + m_impl->get_paths().git_checkout_port(m_impl->get_filesystem(), version_spec.port_name, git_tree); + + auto maybe_control_file = Paragraphs::try_load_port(m_impl->get_filesystem(), port_directory); + if (auto scf = maybe_control_file.get()) + { + if (scf->get()->core_paragraph->name == version_spec.port_name) + { + return m_impl->control_cache + .emplace(version_spec, SourceControlFileLocation{std::move(*scf), std::move(port_directory)}) + .first->second; + } + return Strings::format("Error: Failed to load port from %s: names did not match: '%s' != '%s'", + fs::u8string(port_directory), + version_spec.port_name, + scf->get()->core_paragraph->name); + } + + print_error_message(maybe_control_file.error()); + return Strings::format( + "Error: Failed to load port %s from %s", version_spec.port_name, fs::u8string(port_directory)); + } + + BaselineProvider::BaselineProvider(const VcpkgPaths& paths, const std::string& baseline) + : m_impl(std::make_unique<details::BaselineProviderImpl>(paths, baseline)) + { + } + BaselineProvider::~BaselineProvider() { } + + Optional<VersionT> BaselineProvider::get_baseline_version(StringView port_name) const + { + const auto& cache = m_impl->get_baseline_cache(); + auto it = cache.find(port_name.to_string()); + if (it != cache.end()) + { + return it->second; + } + return nullopt; + } } diff --git a/toolsrc/src/vcpkg/registries.cpp b/toolsrc/src/vcpkg/registries.cpp index e481685d9..7794ed9b0 100644 --- a/toolsrc/src/vcpkg/registries.cpp +++ b/toolsrc/src/vcpkg/registries.cpp @@ -5,7 +5,9 @@ #include <vcpkg/configurationdeserializer.h> #include <vcpkg/registries.h> +#include <vcpkg/vcpkgcmdarguments.h> #include <vcpkg/vcpkgpaths.h> +#include <vcpkg/versiondeserializers.h> #include <vcpkg/versiont.h> #include <map> @@ -70,32 +72,6 @@ namespace } }; - struct VersionTDeserializer final : Json::IDeserializer<VersionT> - { - StringView type_name() const override { return "a version object"; } - View<StringView> valid_fields() const override - { - static const StringView t[] = {"version-string", "port-version"}; - return t; - } - - Optional<VersionT> visit_object(Json::Reader& r, const Json::Object& obj) override - { - std::string version; - int port_version = 0; - - r.required_object_field(type_name(), obj, "version-string", version, version_deserializer); - r.optional_object_field(obj, "port-version", port_version, Json::NaturalNumberDeserializer::instance); - - return VersionT{std::move(version), port_version}; - } - - static Json::StringDeserializer version_deserializer; - static VersionTDeserializer instance; - }; - Json::StringDeserializer VersionTDeserializer::version_deserializer{"version"}; - VersionTDeserializer VersionTDeserializer::instance; - struct FilesystemVersionEntryDeserializer final : Json::IDeserializer<std::pair<VersionT, fs::path>> { StringView type_name() const override { return "a version entry object"; } @@ -109,7 +85,7 @@ namespace { fs::path registry_path; - auto version = VersionTDeserializer::instance.visit_object(r, obj); + auto version = get_versiont_deserializer_instance().visit_object(r, obj); r.required_object_field( "version entry", obj, "registry-path", registry_path, Json::PathDeserializer::instance); @@ -162,30 +138,6 @@ namespace const fs::path& registry_root; }; - struct BaselineDeserializer final : Json::IDeserializer<std::map<std::string, VersionT, std::less<>>> - { - StringView type_name() const override { return "a baseline object"; } - - Optional<type> visit_object(Json::Reader& r, const Json::Object& obj) override - { - std::map<std::string, VersionT, std::less<>> result; - - for (auto pr : obj) - { - const auto& version_value = pr.second; - VersionT version; - r.visit_in_key(version_value, pr.first, version, VersionTDeserializer::instance); - - result.emplace(pr.first.to_string(), std::move(version)); - } - - return std::move(result); - } - - static BaselineDeserializer instance; - }; - BaselineDeserializer BaselineDeserializer::instance; - struct FilesystemRegistry final : RegistryImpl { std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths& paths, StringView port_name) const override @@ -267,6 +219,12 @@ namespace Optional<VersionT> get_baseline_version(const VcpkgPaths& paths, StringView port_name) const override { + if (!paths.get_feature_flags().versions) + { + Checks::check_exit(VCPKG_LINE_INFO, + "This invocation failed because the `versions` feature flag is not enabled."); + } + const auto& baseline_cache = baseline.get([this, &paths] { return load_baseline_versions(paths); }); auto it = baseline_cache.find(port_name); if (it != baseline_cache.end()) @@ -310,26 +268,17 @@ namespace Checks::exit_with_message(VCPKG_LINE_INFO, "Error: `baseline.json` does not have a top-level object."); } - const auto& obj = value.first.object(); - auto baseline_value = obj.get("default"); - if (!baseline_value) - { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: `baseline.json` does not contain the baseline \"%s\"", "default"); - } - - Json::Reader r; - std::map<std::string, VersionT, std::less<>> result; - r.visit_in_key(*baseline_value, "default", result, BaselineDeserializer::instance); - - if (r.errors().empty()) + auto maybe_baseline_versions = parse_baseline_file(paths.get_filesystem(), "default", baseline_file); + if (auto baseline_versions = maybe_baseline_versions.get()) { - return result; + return std::move(*baseline_versions); } else { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: failed to parse `baseline.json`:\n%s", Strings::join("\n", r.errors())); + Checks::exit_with_message(VCPKG_LINE_INFO, + "Error: failed to parse `%s`:\n%s", + fs::u8string(baseline_file), + maybe_baseline_versions.error()); } } diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index a3ab21629..5b33b8db4 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -71,6 +71,15 @@ namespace return result; } + System::CmdLineBuilder git_cmd_builder(const VcpkgPaths& paths, + const fs::path& dot_git_dir, + const fs::path& work_tree) + { + return System::CmdLineBuilder() + .path_arg(paths.get_tool_exe(Tools::GIT)) + .string_arg(Strings::concat("--git-dir=", fs::u8string(dot_git_dir))) + .string_arg(Strings::concat("--work-tree=", fs::u8string(work_tree))); + } } // unnamed namespace namespace vcpkg @@ -353,6 +362,18 @@ If you wish to silence this error and use classic mode, you can: vcpkg_dir_info = vcpkg_dir / fs::u8path("info"); vcpkg_dir_updates = vcpkg_dir / fs::u8path("updates"); + // Versioning paths + const auto versioning_tmp = buildtrees / fs::u8path("versioning_tmp"); + const auto versioning_output = buildtrees / fs::u8path("versioning"); + + baselines_dot_git_dir = versioning_tmp / fs::u8path(".baselines.git"); + baselines_work_tree = versioning_tmp / fs::u8path("baselines-worktree"); + baselines_output = versioning_output / fs::u8path("baselines"); + + versions_dot_git_dir = versioning_tmp / fs::u8path(".versions.git"); + versions_work_tree = versioning_tmp / fs::u8path("versions-worktree"); + versions_output = versioning_output / fs::u8path("versions"); + ports_cmake = filesystem.canonical(VCPKG_LINE_INFO, scripts / fs::u8path("ports.cmake")); for (auto&& overlay_triplets_dir : args.overlay_triplets) @@ -456,6 +477,178 @@ If you wish to silence this error and use classic mode, you can: return m_pimpl->m_tool_cache->get_tool_version(*this, tool); } + void VcpkgPaths::git_checkout_subpath(const VcpkgPaths& paths, + StringView commit_sha, + const fs::path& subpath, + const fs::path& local_repo, + const fs::path& destination, + const fs::path& dot_git_dir, + const fs::path& work_tree) + { + Files::Filesystem& fs = paths.get_filesystem(); + fs.remove_all(work_tree, VCPKG_LINE_INFO); + fs.remove_all(destination, VCPKG_LINE_INFO); + fs.remove_all(dot_git_dir, VCPKG_LINE_INFO); + + // All git commands are run with: --git-dir={dot_git_dir} --work-tree={work_tree_temp} + // git clone --no-checkout --local {vcpkg_root} {dot_git_dir} + System::CmdLineBuilder clone_cmd_builder = git_cmd_builder(paths, dot_git_dir, work_tree) + .string_arg("clone") + .string_arg("--no-checkout") + .string_arg("--local") + .path_arg(local_repo) + .path_arg(dot_git_dir); + const auto clone_output = System::cmd_execute_and_capture_output(clone_cmd_builder.extract()); + Checks::check_exit(VCPKG_LINE_INFO, + clone_output.exit_code == 0, + "Failed to clone temporary vcpkg instance.\n%s\n", + clone_output.output); + + // git checkout {commit-sha} -- {subpath} + System::CmdLineBuilder checkout_cmd_builder = git_cmd_builder(paths, dot_git_dir, work_tree) + .string_arg("checkout") + .string_arg(commit_sha) + .string_arg("--") + .path_arg(subpath); + const auto checkout_output = System::cmd_execute_and_capture_output(checkout_cmd_builder.extract()); + Checks::check_exit(VCPKG_LINE_INFO, + checkout_output.exit_code == 0, + "Error: Failed to checkout %s:%s\n%s\n", + commit_sha, + fs::u8string(subpath), + checkout_output.output); + + const fs::path checked_out_path = work_tree / subpath; + const auto& containing_folder = destination.parent_path(); + if (!fs.exists(containing_folder)) + { + fs.create_directories(containing_folder, VCPKG_LINE_INFO); + } + + std::error_code ec; + fs.rename_or_copy(checked_out_path, destination, ".tmp", ec); + fs.remove_all(work_tree, VCPKG_LINE_INFO); + fs.remove_all(dot_git_dir, VCPKG_LINE_INFO); + if (ec) + { + System::printf(System::Color::error, + "Error: Couldn't move checked out files from %s to destination %s", + fs::u8string(checked_out_path), + fs::u8string(destination)); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + + void VcpkgPaths::git_checkout_object(const VcpkgPaths& paths, + StringView git_object, + const fs::path& local_repo, + const fs::path& destination, + const fs::path& dot_git_dir, + const fs::path& work_tree) + { + Files::Filesystem& fs = paths.get_filesystem(); + fs.remove_all(work_tree, VCPKG_LINE_INFO); + fs.remove_all(destination, VCPKG_LINE_INFO); + + if (!fs.exists(dot_git_dir)) + { + // All git commands are run with: --git-dir={dot_git_dir} --work-tree={work_tree_temp} + // git clone --no-checkout --local {vcpkg_root} {dot_git_dir} + System::CmdLineBuilder clone_cmd_builder = git_cmd_builder(paths, dot_git_dir, work_tree) + .string_arg("clone") + .string_arg("--no-checkout") + .string_arg("--local") + .path_arg(local_repo) + .path_arg(dot_git_dir); + const auto clone_output = System::cmd_execute_and_capture_output(clone_cmd_builder.extract()); + Checks::check_exit(VCPKG_LINE_INFO, + clone_output.exit_code == 0, + "Failed to clone temporary vcpkg instance.\n%s\n", + clone_output.output); + } + else + { + System::CmdLineBuilder fetch_cmd_builder = + git_cmd_builder(paths, dot_git_dir, work_tree).string_arg("fetch"); + const auto fetch_output = System::cmd_execute_and_capture_output(fetch_cmd_builder.extract()); + Checks::check_exit(VCPKG_LINE_INFO, + fetch_output.exit_code == 0, + "Failed to update refs on temporary vcpkg repository.\n%s\n", + fetch_output.output); + } + + if (!fs.exists(work_tree)) + { + fs.create_directories(work_tree, VCPKG_LINE_INFO); + } + + // git checkout {tree_object} . + System::CmdLineBuilder checkout_cmd_builder = git_cmd_builder(paths, dot_git_dir, work_tree) + .string_arg("checkout") + .string_arg(git_object) + .string_arg("."); + const auto checkout_output = System::cmd_execute_and_capture_output(checkout_cmd_builder.extract()); + Checks::check_exit(VCPKG_LINE_INFO, checkout_output.exit_code == 0, "Failed to checkout %s", git_object); + + const auto& containing_folder = destination.parent_path(); + if (!fs.exists(containing_folder)) + { + fs.create_directories(containing_folder, VCPKG_LINE_INFO); + } + + std::error_code ec; + fs.rename_or_copy(work_tree, destination, ".tmp", ec); + fs.remove_all(work_tree, VCPKG_LINE_INFO); + if (ec) + { + System::printf(System::Color::error, + "Error: Couldn't move checked out files from %s to destination %s", + fs::u8string(work_tree), + fs::u8string(destination)); + Checks::exit_fail(VCPKG_LINE_INFO); + } + } + + fs::path VcpkgPaths::git_checkout_baseline(Files::Filesystem& fs, StringView commit_sha) const + { + const fs::path local_repo = this->root; + const fs::path destination = this->baselines_output / fs::u8path(commit_sha) / fs::u8path("baseline.json"); + const fs::path baseline_subpath = fs::u8path("port_versions") / fs::u8path("baseline.json"); + + if (!fs.exists(destination)) + { + git_checkout_subpath(*this, + commit_sha, + baseline_subpath, + local_repo, + destination, + this->baselines_dot_git_dir, + this->baselines_work_tree); + } + return destination; + } + + fs::path VcpkgPaths::git_checkout_port(Files::Filesystem& fs, StringView port_name, StringView git_tree) const + { + /* Clone a new vcpkg repository instance using the local instance as base. + * + * The `--git-dir` directory will store all the Git metadata files, + * and the `--work-tree` is the directory where files will be checked out. + * + * Since we are checking a git tree object, all files will be checked out to the root of `work-tree`. + * Because of that, it makes sense to use the git hash as the name for the directory. + */ + const fs::path local_repo = this->root; + const fs::path destination = this->versions_output / fs::u8path(git_tree) / fs::u8path(port_name); + + if (!fs.exists(destination / "CONTROL") && !fs.exists(destination / "vcpkg.json")) + { + git_checkout_object( + *this, git_tree, local_repo, destination, this->versions_dot_git_dir, this->versions_work_tree); + } + return destination; + } + Optional<const Json::Object&> VcpkgPaths::get_manifest() const { if (auto p = m_pimpl->m_manifest_doc.get()) diff --git a/toolsrc/src/vcpkg/versiondeserializers.cpp b/toolsrc/src/vcpkg/versiondeserializers.cpp new file mode 100644 index 000000000..276b70e4f --- /dev/null +++ b/toolsrc/src/vcpkg/versiondeserializers.cpp @@ -0,0 +1,210 @@ +#include <vcpkg/versiondeserializers.h> + +using namespace vcpkg; +using namespace vcpkg::Versions; + +namespace +{ + struct VersionDbEntryDeserializer final : Json::IDeserializer<VersionDbEntry> + { + static constexpr StringLiteral VERSION_RELAXED = "version"; + static constexpr StringLiteral VERSION_SEMVER = "version-semver"; + static constexpr StringLiteral VERSION_STRING = "version-string"; + static constexpr StringLiteral VERSION_DATE = "version-date"; + static constexpr StringLiteral PORT_VERSION = "port-version"; + static constexpr StringLiteral GIT_TREE = "git-tree"; + + StringView type_name() const override { return "a version database entry"; } + View<StringView> valid_fields() const override + { + static const StringView t[] = { + VERSION_RELAXED, VERSION_SEMVER, VERSION_STRING, VERSION_DATE, PORT_VERSION, GIT_TREE}; + return t; + } + + Optional<VersionDbEntry> visit_object(Json::Reader& r, const Json::Object& obj) override + { + std::string version; + int port_version = 0; + std::string git_tree; + Versions::Scheme version_scheme = Versions::Scheme::String; + + // Code copy-and-paste'd from sourceparagraph.cpp + static Json::StringDeserializer version_exact_deserializer{"an exact version string"}; + static Json::StringDeserializer version_relaxed_deserializer{"a relaxed version string"}; + static Json::StringDeserializer version_semver_deserializer{"a semantic version string"}; + static Json::StringDeserializer version_date_deserializer{"a date version string"}; + static Json::StringDeserializer git_tree_deserializer("a git object SHA"); + + bool has_exact = r.optional_object_field(obj, VERSION_STRING, version, version_exact_deserializer); + bool has_relax = r.optional_object_field(obj, VERSION_RELAXED, version, version_relaxed_deserializer); + bool has_semver = r.optional_object_field(obj, VERSION_SEMVER, version, version_semver_deserializer); + bool has_date = r.optional_object_field(obj, VERSION_DATE, version, version_date_deserializer); + int num_versions = (int)has_exact + (int)has_relax + (int)has_semver + (int)has_date; + if (num_versions == 0) + { + r.add_generic_error(type_name(), "expected a versioning field (example: ", VERSION_STRING, ")"); + } + else if (num_versions > 1) + { + r.add_generic_error(type_name(), "expected only one versioning field"); + } + else + { + if (has_exact) + version_scheme = Versions::Scheme::String; + else if (has_relax) + version_scheme = Versions::Scheme::Relaxed; + else if (has_semver) + version_scheme = Versions::Scheme::Semver; + else if (has_date) + version_scheme = Versions::Scheme::Date; + else + Checks::unreachable(VCPKG_LINE_INFO); + } + r.optional_object_field(obj, PORT_VERSION, port_version, Json::NaturalNumberDeserializer::instance); + r.required_object_field(type_name(), obj, GIT_TREE, git_tree, git_tree_deserializer); + + return VersionDbEntry(version, port_version, version_scheme, git_tree); + } + + static VersionDbEntryDeserializer instance; + }; + + struct VersionDbEntryArrayDeserializer final : Json::IDeserializer<std::vector<VersionDbEntry>> + { + virtual StringView type_name() const override { return "an array of versions"; } + + virtual Optional<std::vector<VersionDbEntry>> visit_array(Json::Reader& r, const Json::Array& arr) override + { + return r.array_elements(arr, VersionDbEntryDeserializer::instance); + } + + static VersionDbEntryArrayDeserializer instance; + }; + + VersionDbEntryDeserializer VersionDbEntryDeserializer::instance; + VersionDbEntryArrayDeserializer VersionDbEntryArrayDeserializer::instance; + + struct BaselineDeserializer final : Json::IDeserializer<std::map<std::string, VersionT, std::less<>>> + { + StringView type_name() const override { return "a baseline object"; } + + Optional<type> visit_object(Json::Reader& r, const Json::Object& obj) override + { + std::map<std::string, VersionT, std::less<>> result; + + for (auto&& pr : obj) + { + const auto& version_value = pr.second; + VersionT version; + r.visit_in_key(version_value, pr.first, version, get_versiont_deserializer_instance()); + + result.emplace(pr.first.to_string(), std::move(version)); + } + + return std::move(result); + } + + static BaselineDeserializer instance; + }; + BaselineDeserializer BaselineDeserializer::instance; + + struct VersionTDeserializer final : Json::IDeserializer<VersionT> + { + StringView type_name() const override { return "a version object"; } + View<StringView> valid_fields() const override + { + static const StringView t[] = {"version-string", "port-version"}; + return t; + } + + Optional<VersionT> visit_object(Json::Reader& r, const Json::Object& obj) override + { + std::string version; + int port_version = 0; + + r.required_object_field(type_name(), obj, "version-string", version, version_deserializer); + r.optional_object_field(obj, "port-version", port_version, Json::NaturalNumberDeserializer::instance); + + return VersionT{std::move(version), port_version}; + } + + static Json::StringDeserializer version_deserializer; + static VersionTDeserializer instance; + }; + Json::StringDeserializer VersionTDeserializer::version_deserializer{"version"}; + VersionTDeserializer VersionTDeserializer::instance; +} + +namespace vcpkg +{ + Json::IDeserializer<VersionT>& get_versiont_deserializer_instance() { return VersionTDeserializer::instance; } + + ExpectedS<std::map<std::string, VersionT, std::less<>>> parse_baseline_file(Files::Filesystem& fs, + StringView baseline_name, + const fs::path& baseline_file_path) + { + if (!fs.exists(baseline_file_path)) + { + return Strings::format("Couldn't find `%s`", fs::u8string(baseline_file_path)); + } + + auto value = Json::parse_file(VCPKG_LINE_INFO, fs, baseline_file_path); + if (!value.first.is_object()) + { + return Strings::format("Error: `%s` does not have a top-level object.", fs::u8string(baseline_file_path)); + } + + const auto& obj = value.first.object(); + auto baseline_value = obj.get(baseline_name); + if (!baseline_value) + { + return Strings::format( + "Error: `%s` does not contain the baseline \"%s\"", fs::u8string(baseline_file_path), baseline_name); + } + + Json::Reader r; + std::map<std::string, VersionT, std::less<>> result; + r.visit_in_key(*baseline_value, baseline_name, result, BaselineDeserializer::instance); + if (!r.errors().empty()) + { + return Strings::format( + "Error: failed to parse `%s`:\n%s", fs::u8string(baseline_file_path), Strings::join("\n", r.errors())); + } + return result; + } + + ExpectedS<std::vector<VersionDbEntry>> parse_versions_file(Files::Filesystem& fs, + StringView port_name, + const fs::path& versions_file_path) + { + (void)port_name; + if (!fs.exists(versions_file_path)) + { + return Strings::format("Couldn't find the versions database file: %s", fs::u8string(versions_file_path)); + } + + auto versions_json = Json::parse_file(VCPKG_LINE_INFO, fs, versions_file_path); + if (!versions_json.first.is_object()) + { + return Strings::format("Error: `%s` does not have a top level object.", fs::u8string(versions_file_path)); + } + + const auto& versions_object = versions_json.first.object(); + auto maybe_versions_array = versions_object.get("versions"); + if (!maybe_versions_array || !maybe_versions_array->is_array()) + { + return Strings::format("Error: `%s` does not contain a versions array.", fs::u8string(versions_file_path)); + } + + std::vector<VersionDbEntry> db_entries; + // Avoid warning treated as error. + if (maybe_versions_array != nullptr) + { + Json::Reader r; + r.visit_in_key(*maybe_versions_array, "versions", db_entries, VersionDbEntryArrayDeserializer::instance); + } + return db_entries; + } +} diff --git a/toolsrc/src/vcpkg/versions.cpp b/toolsrc/src/vcpkg/versions.cpp new file mode 100644 index 000000000..ac1829712 --- /dev/null +++ b/toolsrc/src/vcpkg/versions.cpp @@ -0,0 +1,34 @@ +#include <vcpkg/versions.h> + +namespace vcpkg::Versions +{ + VersionSpec::VersionSpec(const std::string& port_name, const VersionT& version, Scheme scheme) + : port_name(port_name), version(version), scheme(scheme) + { + } + + VersionSpec::VersionSpec(const std::string& port_name, + const std::string& version_string, + int port_version, + Scheme scheme) + : port_name(port_name), version(version_string, port_version), scheme(scheme) + { + } + + bool operator==(const VersionSpec& lhs, const VersionSpec& rhs) + { + return std::tie(lhs.port_name, lhs.version, lhs.scheme) == std::tie(rhs.port_name, rhs.version, rhs.scheme); + } + + bool operator!=(const VersionSpec& lhs, const VersionSpec& rhs) { return !(lhs == rhs); } + + std::size_t VersionSpecHasher::operator()(const VersionSpec& key) const + { + using std::hash; + using std::size_t; + using std::string; + + return ((hash<string>()(key.port_name) ^ (hash<string>()(key.version.to_string()) << 1)) >> 1) ^ + (hash<int>()(static_cast<int>(key.scheme)) << 1); + } +}
\ No newline at end of file |
