diff options
| author | nicole mazzuca <mazzucan@outlook.com> | 2021-01-14 19:50:31 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-14 19:50:31 -0800 |
| commit | a5971344505757e4868e14d112362326610b98e5 (patch) | |
| tree | 2bfe83c1ff297b4e5335ac90a11e0e9adf72e823 /toolsrc/src | |
| parent | 2f6537fa2b8928d2329e827f862692112793435d (diff) | |
| download | vcpkg-a5971344505757e4868e14d112362326610b98e5.tar.gz vcpkg-a5971344505757e4868e14d112362326610b98e5.zip | |
[vcpkg registries] Add git registries (#15054)
* [vcpkg registries] Add git registries support
* Add git registries support to the registries module of vcpkg.
* add e2e tests for git registries
* fix vcpkg.cmake for registries
* fix CRs, remove a thing
* better error messages
* Billy CRs
* fix Robert's CR comment
* I learned about `-c` today
* format
* fix baseline.json
* failing to find baseline is technically not a bug
Diffstat (limited to 'toolsrc/src')
| -rw-r--r-- | toolsrc/src/vcpkg/base/system.cpp | 9 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/install.cpp | 10 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/registries.cpp | 241 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/vcpkgpaths.cpp | 159 |
4 files changed, 401 insertions, 18 deletions
diff --git a/toolsrc/src/vcpkg/base/system.cpp b/toolsrc/src/vcpkg/base/system.cpp index 9429752be..c400815be 100644 --- a/toolsrc/src/vcpkg/base/system.cpp +++ b/toolsrc/src/vcpkg/base/system.cpp @@ -10,6 +10,15 @@ using namespace vcpkg::System; namespace vcpkg { + long System::get_process_id() + { +#ifdef _WIN32 + return ::_getpid(); +#else + return ::getpid(); +#endif + } + Optional<CPUArchitecture> System::to_cpu_architecture(StringView arch) { if (Strings::case_insensitive_ascii_equals(arch, "x86")) return CPUArchitecture::X86; diff --git a/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index 51c7b8289..80b4eae3a 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/src/vcpkg/install.cpp @@ -1,5 +1,6 @@ #include <vcpkg/base/files.h> #include <vcpkg/base/hash.h> +#include <vcpkg/base/system.debug.h> #include <vcpkg/base/system.print.h> #include <vcpkg/base/util.h> @@ -844,15 +845,18 @@ namespace vcpkg::Install features.erase(core_it); } - if (args.versions_enabled()) + if (args.versions_enabled() || args.registries_enabled()) { - auto verprovider = PortFileProvider::make_versioned_portfile_provider(paths); - auto baseprovider = PortFileProvider::make_baseline_provider(paths); if (auto p_baseline = manifest_scf.core_paragraph->extra_info.get("$x-default-baseline")) { paths.get_configuration().registry_set.experimental_set_builtin_registry_baseline( p_baseline->string()); } + } + if (args.versions_enabled()) + { + auto verprovider = PortFileProvider::make_versioned_portfile_provider(paths); + auto baseprovider = PortFileProvider::make_baseline_provider(paths); auto oprovider = PortFileProvider::make_overlay_provider(paths, args.overlay_ports); auto install_plan = diff --git a/toolsrc/src/vcpkg/registries.cpp b/toolsrc/src/vcpkg/registries.cpp index 7f84b5075..b9afa089b 100644 --- a/toolsrc/src/vcpkg/registries.cpp +++ b/toolsrc/src/vcpkg/registries.cpp @@ -25,10 +25,13 @@ namespace // when `BuiltinRegistryEntry` is using a port versions file for a port, // it uses this as it's underlying type; // when `BuiltinRegistryEntry` is using a port tree, it uses the scfl - struct GitRegistryEntry + struct GitRegistryEntry final : RegistryEntry { explicit GitRegistryEntry(std::string&& port_name) : port_name(port_name) { } + View<VersionT> get_port_versions() const override { return port_versions; } + ExpectedS<fs::path> get_path_to_version(const VcpkgPaths&, const VersionT& version) const override; + std::string port_name; // these two map port versions to git trees @@ -37,6 +40,67 @@ namespace std::vector<std::string> git_trees; }; + struct GitRegistry final : RegistryImplementation + { + GitRegistry(std::string&& repo, std::string&& baseline) + : m_repo(std::move(repo)), m_baseline_identifier(std::move(baseline)) + { + } + + std::unique_ptr<RegistryEntry> get_port_entry(const VcpkgPaths&, StringView) const override; + + void get_all_port_names(std::vector<std::string>&, const VcpkgPaths&) const override; + + Optional<VersionT> get_baseline_version(const VcpkgPaths&, StringView) const override; + + StringView get_commit_of_repo(const VcpkgPaths& paths) const + { + return m_commit.get([this, &paths]() -> std::string { + auto maybe_hash = paths.git_fetch_from_remote_registry(m_repo); + if (!maybe_hash.has_value()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Error: Failed to fetch from remote registry `%s`: %s", + m_repo, + maybe_hash.error()); + } + return std::move(*maybe_hash.get()); + }); + } + + fs::path get_versions_tree_path(const VcpkgPaths& paths) const + { + return m_versions_tree.get([this, &paths]() -> fs::path { + auto maybe_tree = paths.git_find_object_id_for_remote_registry_path(get_commit_of_repo(paths), + fs::u8path("port_versions")); + if (!maybe_tree) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Error: could not find the git tree for `port_versions` in repo `%s` at commit `%s`: %s", + m_repo, + get_commit_of_repo(paths), + maybe_tree.error()); + } + auto maybe_path = paths.git_checkout_object_from_remote_registry(*maybe_tree.get()); + if (!maybe_path) + { + Checks::exit_with_message(VCPKG_LINE_INFO, + "Error: failed to check out `port_versions` from repo %s: %s", + m_repo, + maybe_path.error()); + } + return std::move(*maybe_path.get()); + }); + } + + std::string m_repo; + DelayedInit<std::string> m_commit; // TODO: eventually this should end up in the vcpkg-lock.json file + DelayedInit<fs::path> m_versions_tree; + std::string m_baseline_identifier; + DelayedInit<Baseline> m_baseline; + }; + struct BuiltinRegistryEntry final : RegistryEntry { explicit BuiltinRegistryEntry(std::unique_ptr<GitRegistryEntry>&& entry) @@ -195,8 +259,7 @@ namespace std::unique_ptr<RegistryEntry> BuiltinRegistry::get_port_entry(const VcpkgPaths& paths, StringView port_name) const { auto versions_path = paths.builtin_port_versions / relative_path_to_versions(port_name); - if ((paths.get_feature_flags().registries || paths.get_feature_flags().versions) && - paths.get_filesystem().exists(versions_path)) + if (!m_baseline_identifier.empty() && paths.get_filesystem().exists(versions_path)) { auto maybe_version_entries = load_versions_file(paths.get_filesystem(), VersionDbType::Git, paths.builtin_port_versions, port_name); @@ -247,6 +310,7 @@ namespace ExpectedS<Baseline> try_parse_builtin_baseline(const VcpkgPaths& paths, StringView baseline_identifier) { + if (baseline_identifier.size() == 0) return Baseline{}; auto path_to_baseline = paths.builtin_port_versions / fs::u8path("baseline.json"); auto res_baseline = load_baseline_versions(paths, path_to_baseline, baseline_identifier); @@ -261,15 +325,10 @@ namespace return std::move(*p); } - if (baseline_identifier.size() == 0) - { - return {{}, expected_left_tag}; - } - if (baseline_identifier == "default") { - return Strings::format("Couldn't find explicitly specified baseline `\"default\"` in the baseline file.", - baseline_identifier); + return Strings::format("Couldn't find explicitly specified baseline `\"default\"` in baseline file: %s", + fs::u8string(path_to_baseline)); } // attempt to check out the baseline: @@ -305,6 +364,7 @@ namespace } Optional<VersionT> BuiltinRegistry::get_baseline_version(const VcpkgPaths& paths, StringView port_name) const { + Debug::print("Baseline version: \"", m_baseline_identifier, "\"\n"); if (!m_baseline_identifier.empty()) { const auto& baseline = m_baseline.get( @@ -318,7 +378,7 @@ namespace } else { - // fall back to using the ports directory version + // if a baseline is not specified, use the ports directory version auto maybe_scf = Paragraphs::try_load_port(paths.get_filesystem(), paths.builtin_ports_directory() / fs::u8path(port_name)); if (auto pscf = maybe_scf.get()) @@ -333,8 +393,7 @@ namespace void BuiltinRegistry::get_all_port_names(std::vector<std::string>& out, const VcpkgPaths& paths) const { - if ((paths.get_feature_flags().registries || paths.get_feature_flags().versions) && - paths.get_filesystem().exists(paths.builtin_port_versions)) + if (!m_baseline_identifier.empty() && paths.get_filesystem().exists(paths.builtin_port_versions)) { load_all_port_names_from_port_versions(out, paths, paths.builtin_port_versions); } @@ -415,6 +474,118 @@ namespace } // } FilesystemRegistry::RegistryImplementation + // { GitRegistry::RegistryImplementation + std::unique_ptr<RegistryEntry> GitRegistry::get_port_entry(const VcpkgPaths& paths, StringView port_name) const + { + auto port_versions = get_versions_tree_path(paths); + auto maybe_version_entries = + load_versions_file(paths.get_filesystem(), VersionDbType::Git, port_versions, port_name); + Checks::check_exit( + VCPKG_LINE_INFO, maybe_version_entries.has_value(), "Error: %s", maybe_version_entries.error()); + auto version_entries = std::move(maybe_version_entries).value_or_exit(VCPKG_LINE_INFO); + + auto res = std::make_unique<GitRegistryEntry>(port_name.to_string()); + for (auto&& version_entry : version_entries) + { + res->port_versions.push_back(version_entry.version); + res->git_trees.push_back(version_entry.git_tree); + } + return res; + } + + Optional<VersionT> GitRegistry::get_baseline_version(const VcpkgPaths& paths, StringView port_name) const + { + const auto& baseline = m_baseline.get([this, &paths]() -> Baseline { + auto baseline_file = get_versions_tree_path(paths) / fs::u8path("baseline.json"); + + auto res_baseline = load_baseline_versions(paths, baseline_file, m_baseline_identifier); + + if (!res_baseline.has_value()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error()); + } + auto opt_baseline = res_baseline.get(); + if (auto p = opt_baseline->get()) + { + return std::move(*p); + } + + if (m_baseline_identifier.empty()) + { + return {}; + } + + if (m_baseline_identifier == "default") + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Couldn't find explicitly specified baseline `\"default\"` in the baseline file.", + m_baseline_identifier); + } + + // attempt to check out the baseline: + auto explicit_hash = paths.git_fetch_from_remote_registry(m_repo, m_baseline_identifier); + if (!explicit_hash.has_value()) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Error: Couldn't find explicitly specified baseline `\"%s\"` in the baseline file for repo %s, " + "and that commit doesn't exist.\n%s", + m_baseline_identifier, + m_repo, + explicit_hash.error()); + } + auto path_to_baseline = fs::u8path("port_versions") / fs::u8path("baseline.json"); + auto maybe_contents = paths.git_show_from_remote_registry(*explicit_hash.get(), path_to_baseline); + if (!maybe_contents.has_value()) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Error: Couldn't find explicitly specified baseline `\"%s\"` in the baseline file for repo %s, " + "and the baseline file doesn't exist at that commit.\n%s\n", + m_baseline_identifier, + m_repo, + maybe_contents.error()); + } + + auto contents = maybe_contents.get(); + res_baseline = parse_baseline_versions(*contents, {}); + if (!res_baseline.has_value()) + { + Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error()); + } + opt_baseline = res_baseline.get(); + if (auto p = opt_baseline->get()) + { + return std::move(*p); + } + else + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Couldn't find explicitly specified baseline `\"%s\"` in the baseline file for repo %s, " + "and the `\"default\"` baseline does not exist at that commit.", + m_baseline_identifier, + m_repo); + } + }); + + auto it = baseline.find(port_name); + if (it != baseline.end()) + { + return it->second; + } + + return nullopt; + } + + void GitRegistry::get_all_port_names(std::vector<std::string>& out, const VcpkgPaths& paths) const + { + auto versions_path = get_versions_tree_path(paths); + load_all_port_names_from_port_versions(out, paths, versions_path); + } + // } GitRegistry::RegistryImplementation + // } RegistryImplementation // { RegistryEntry @@ -464,6 +635,20 @@ namespace } // } FilesystemRegistryEntry::RegistryEntry + // { GitRegistryEntry::RegistryEntry + ExpectedS<fs::path> GitRegistryEntry::get_path_to_version(const VcpkgPaths& paths, const VersionT& version) const + { + auto it = std::find(port_versions.begin(), port_versions.end(), version); + if (it == port_versions.end()) + { + return Strings::concat("Error: No version entry for ", port_name, " at version ", version, "."); + } + + const auto& git_tree = git_trees[it - port_versions.begin()]; + return paths.git_checkout_object_from_remote_registry(git_tree); + } + // } GitRegistryEntry::RegistryEntry + // } RegistryEntry } @@ -601,9 +786,11 @@ namespace constexpr static StringLiteral KIND = "kind"; constexpr static StringLiteral BASELINE = "baseline"; constexpr static StringLiteral PATH = "path"; + constexpr static StringLiteral REPO = "repository"; constexpr static StringLiteral KIND_BUILTIN = "builtin"; constexpr static StringLiteral KIND_FILESYSTEM = "filesystem"; + constexpr static StringLiteral KIND_GIT = "git"; virtual StringView type_name() const override { return "a registry"; } virtual View<StringView> valid_fields() const override; @@ -620,8 +807,10 @@ namespace constexpr StringLiteral RegistryImplDeserializer::KIND; constexpr StringLiteral RegistryImplDeserializer::BASELINE; constexpr StringLiteral RegistryImplDeserializer::PATH; + constexpr StringLiteral RegistryImplDeserializer::REPO; constexpr StringLiteral RegistryImplDeserializer::KIND_BUILTIN; constexpr StringLiteral RegistryImplDeserializer::KIND_FILESYSTEM; + constexpr StringLiteral RegistryImplDeserializer::KIND_GIT; struct RegistryDeserializer final : Json::IDeserializer<Registry> { @@ -640,7 +829,7 @@ namespace View<StringView> RegistryImplDeserializer::valid_fields() const { - static const StringView t[] = {KIND, BASELINE, PATH}; + static const StringView t[] = {KIND, BASELINE, PATH, REPO}; return t; } View<StringView> valid_builtin_fields() @@ -662,6 +851,16 @@ namespace }; return t; } + View<StringView> valid_git_fields() + { + static const StringView t[] = { + RegistryImplDeserializer::KIND, + RegistryImplDeserializer::BASELINE, + RegistryImplDeserializer::REPO, + RegistryDeserializer::PACKAGES, + }; + return t; + } Optional<std::unique_ptr<RegistryImplementation>> RegistryImplDeserializer::visit_null(Json::Reader&) { @@ -695,9 +894,19 @@ namespace res = std::make_unique<FilesystemRegistry>(config_directory / path, std::move(baseline)); } + else if (kind == KIND_GIT) + { + r.check_for_unexpected_fields(obj, valid_git_fields(), "a git registry"); + + std::string repo; + Json::StringDeserializer repo_des{"a git repository URL"}; + r.required_object_field("a git registry", obj, REPO, repo, repo_des); + + res = std::make_unique<GitRegistry>(std::move(repo), std::move(baseline)); + } else { - StringLiteral valid_kinds[] = {KIND_BUILTIN, KIND_FILESYSTEM}; + StringLiteral valid_kinds[] = {KIND_BUILTIN, KIND_FILESYSTEM, KIND_GIT}; r.add_generic_error(type_name(), "Field \"kind\" did not have an expected value (expected one of: \"", Strings::join("\", \"", valid_kinds), @@ -716,6 +925,7 @@ namespace RegistryImplDeserializer::KIND, RegistryImplDeserializer::BASELINE, RegistryImplDeserializer::PATH, + RegistryImplDeserializer::REPO, PACKAGES, }; return t; @@ -853,6 +1063,7 @@ namespace } else if (maybe_contents.error() == std::errc::no_such_file_or_directory) { + Debug::print("Failed to find baseline.json\n"); return {nullopt, expected_left_tag}; } else diff --git a/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index 1c65cd170..afd724765 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/src/vcpkg/vcpkgpaths.cpp @@ -202,6 +202,11 @@ namespace vcpkg , m_env_cache(ff_settings.compiler_tracking) , m_ff_settings(ff_settings) { + const auto& cache_root = + System::get_platform_cache_home().value_or_exit(VCPKG_LINE_INFO) / fs::u8path("vcpkg"); + registries_work_tree_dir = cache_root / fs::u8path("registries") / fs::u8path("git"); + registries_dot_git_dir = registries_work_tree_dir / fs::u8path(".git"); + registries_git_trees = cache_root / fs::u8path("registries") / fs::u8path("git-trees"); } Lazy<std::vector<VcpkgPaths::TripletFile>> available_triplets; @@ -224,6 +229,10 @@ namespace vcpkg Configuration m_config; FeatureFlagSettings m_ff_settings; + + fs::path registries_work_tree_dir; + fs::path registries_dot_git_dir; + fs::path registries_git_trees; }; } @@ -726,6 +735,156 @@ If you wish to silence this error and use classic mode, you can: return destination; } + ExpectedS<std::string> VcpkgPaths::git_fetch_from_remote_registry(StringView repo, StringView treeish) const + { + auto& fs = get_filesystem(); + + auto work_tree = m_pimpl->registries_work_tree_dir; + fs.create_directories(work_tree, VCPKG_LINE_INFO); + auto dot_git_dir = m_pimpl->registries_dot_git_dir; + + System::CmdLineBuilder init_registries_git_dir = + git_cmd_builder(*this, dot_git_dir, work_tree).string_arg("init"); + auto init_output = System::cmd_execute_and_capture_output(init_registries_git_dir); + if (init_output.exit_code != 0) + { + return {Strings::format("Error: Failed to initialize local repository %s.\n%s\n", + fs::u8string(work_tree), + init_output.output), + expected_right_tag}; + } + + auto lock_file = work_tree / fs::u8path(".vcpkg-lock"); + + std::error_code ec; + auto guard = Files::ExclusiveFileLock(Files::ExclusiveFileLock::Wait::Yes, fs, lock_file, ec); + + System::CmdLineBuilder fetch_git_ref = + git_cmd_builder(*this, dot_git_dir, work_tree).string_arg("fetch").string_arg("--").string_arg(repo); + if (treeish.size() != 0) + { + fetch_git_ref.string_arg(treeish); + } + + auto fetch_output = System::cmd_execute_and_capture_output(fetch_git_ref); + if (fetch_output.exit_code != 0) + { + return {Strings::format("Error: Failed to fetch %s%s from repository %s.\n%s\n", + treeish.size() != 0 ? "ref " : "", + treeish, + repo, + fetch_output.output), + expected_right_tag}; + } + + System::CmdLineBuilder get_fetch_head = + git_cmd_builder(*this, dot_git_dir, work_tree).string_arg("rev-parse").string_arg("FETCH_HEAD"); + auto fetch_head_output = System::cmd_execute_and_capture_output(get_fetch_head); + if (fetch_head_output.exit_code != 0) + { + return {Strings::format("Error: Failed to rev-parse FETCH_HEAD.\n%s\n", fetch_head_output.output), + expected_right_tag}; + } + return {Strings::trim(fetch_head_output.output).to_string(), expected_left_tag}; + } + // returns an error if there was an unexpected error; returns nullopt if the file doesn't exist at the specified + // hash + ExpectedS<std::string> VcpkgPaths::git_show_from_remote_registry(StringView hash, + const fs::path& relative_path) const + { + auto revision = Strings::format("%s:%s", hash, fs::generic_u8string(relative_path)); + System::CmdLineBuilder git_show = + git_cmd_builder(*this, m_pimpl->registries_dot_git_dir, m_pimpl->registries_work_tree_dir) + .string_arg("show") + .string_arg(revision); + + auto git_show_output = System::cmd_execute_and_capture_output(git_show); + if (git_show_output.exit_code != 0) + { + return {git_show_output.output, expected_right_tag}; + } + return {git_show_output.output, expected_left_tag}; + } + ExpectedS<std::string> VcpkgPaths::git_find_object_id_for_remote_registry_path(StringView hash, + const fs::path& relative_path) const + { + auto revision = Strings::format("%s:%s", hash, fs::generic_u8string(relative_path)); + System::CmdLineBuilder git_rev_parse = + git_cmd_builder(*this, m_pimpl->registries_dot_git_dir, m_pimpl->registries_work_tree_dir) + .string_arg("rev-parse") + .string_arg(revision); + + auto git_rev_parse_output = System::cmd_execute_and_capture_output(git_rev_parse); + if (git_rev_parse_output.exit_code != 0) + { + return {git_rev_parse_output.output, expected_right_tag}; + } + return {Strings::trim(git_rev_parse_output.output).to_string(), expected_left_tag}; + } + ExpectedS<fs::path> VcpkgPaths::git_checkout_object_from_remote_registry(StringView object) const + { + auto& fs = get_filesystem(); + fs.create_directories(m_pimpl->registries_git_trees, VCPKG_LINE_INFO); + + auto git_tree_final = m_pimpl->registries_git_trees / fs::u8path(object); + if (fs.exists(git_tree_final)) + { + return std::move(git_tree_final); + } + + auto pid = System::get_process_id(); + + fs::path git_tree_temp = fs::u8path(Strings::format("%s.tmp%ld", fs::u8string(git_tree_final), pid)); + fs::path git_tree_temp_tar = fs::u8path(Strings::format("%s.tmp%ld.tar", fs::u8string(git_tree_final), pid)); + fs.remove_all(git_tree_temp, VCPKG_LINE_INFO); + fs.create_directory(git_tree_temp, VCPKG_LINE_INFO); + + auto dot_git_dir = m_pimpl->registries_dot_git_dir; + System::CmdLineBuilder git_archive = git_cmd_builder(*this, dot_git_dir, m_pimpl->registries_work_tree_dir) + .string_arg("archive") + .string_arg("--format") + .string_arg("tar") + .string_arg(object) + .string_arg("--output") + .path_arg(git_tree_temp_tar); + auto git_archive_output = System::cmd_execute_and_capture_output(git_archive); + if (git_archive_output.exit_code != 0) + { + return {Strings::format("git archive failed with message:\n%s", git_archive_output.output), + expected_right_tag}; + } + + auto untar = System::CmdLineBuilder{get_tool_exe(Tools::CMAKE)} + .string_arg("-E") + .string_arg("tar") + .string_arg("xf") + .path_arg(git_tree_temp_tar); + + auto untar_output = System::cmd_execute_and_capture_output(untar, System::InWorkingDirectory{git_tree_temp}); + if (untar_output.exit_code != 0) + { + return {Strings::format("cmake's untar failed with message:\n%s", untar_output.output), expected_right_tag}; + } + + std::error_code ec; + fs.rename(git_tree_temp, git_tree_final, ec); + + if (fs.exists(git_tree_final)) + { + return git_tree_final; + } + if (ec) + { + return { + Strings::format("rename to %s failed with message:\n%s", fs::u8string(git_tree_final), ec.message()), + expected_right_tag}; + } + else + { + return {"Unknown error", expected_right_tag}; + } + } + Optional<const Json::Object&> VcpkgPaths::get_manifest() const { if (auto p = m_pimpl->m_manifest_doc.get()) |
