From 710a2acce5a472f97dce132df9ee73cb72cd593a Mon Sep 17 00:00:00 2001 From: ras0219 <533828+ras0219@users.noreply.github.com> Date: Fri, 15 Jan 2021 12:35:48 -0800 Subject: [PATCH] [vcpkg] Add initial versioning documentation (#15565) * [vcpkg] Improve efficiency and tests of versioning * [vcpkg] Add initial versioning documentation and rename x-default-baseline to builtin-baseline * [vcpkg] Enable metrics for builtin-baseline & overrides * [vcpkg] Address PR comments * [vcpkg] Add support for syntax in version>= * [vcpkg] Remove port-version from dependency syntax * [vcpkg] Address CR comment * [vcpkg] Minor docs fixup --- include/vcpkg/base/expected.h | 9 +- include/vcpkg/base/files.h | 33 ++-- include/vcpkg/base/json.h | 4 +- include/vcpkg/base/jsonreader.h | 2 +- include/vcpkg/sourceparagraph.h | 1 + include/vcpkg/vcpkgpaths.h | 15 +- include/vcpkg/versions.h | 3 +- src/vcpkg-test/dependencies.cpp | 27 +-- src/vcpkg-test/manifests.cpp | 108 +++++++---- src/vcpkg/base/json.cpp | 5 + src/vcpkg/commands.civerifyversions.cpp | 2 +- src/vcpkg/help.cpp | 65 ++++++- src/vcpkg/install.cpp | 22 ++- src/vcpkg/paragraphs.cpp | 9 +- src/vcpkg/registries.cpp | 63 +++---- src/vcpkg/sourceparagraph.cpp | 125 ++++++++----- src/vcpkg/vcpkgpaths.cpp | 232 +++++++++++++++--------- 17 files changed, 468 insertions(+), 257 deletions(-) diff --git a/include/vcpkg/base/expected.h b/include/vcpkg/base/expected.h index 13e7b4bcbf61d3..20c23f07730780 100644 --- a/include/vcpkg/base/expected.h +++ b/include/vcpkg/base/expected.h @@ -226,11 +226,10 @@ namespace vcpkg void exit_if_error(const LineInfo& line_info) const { // This is used for quick value_or_exit() calls, so always put line_info in the error message. - Checks::check_exit(line_info, - !m_s.has_error(), - "Failed at [%s] with message:\n%s", - line_info.to_string(), - m_s.to_string()); + if (m_s.has_error()) + { + Checks::exit_with_message(line_info, "Failed at [%s] with message:\n%s", line_info, m_s.to_string()); + } } ErrorHolder m_s; diff --git a/include/vcpkg/base/files.h b/include/vcpkg/base/files.h index dd1b3a6c615f25..5f2e96e91eba79 100644 --- a/include/vcpkg/base/files.h +++ b/include/vcpkg/base/files.h @@ -282,45 +282,34 @@ namespace vcpkg::Files }; ExclusiveFileLock() = default; - ExclusiveFileLock(ExclusiveFileLock&& other) - : fs_(other.fs_), handle_(std::exchange(handle_, fs::SystemHandle{})) - { - } - ExclusiveFileLock& operator=(ExclusiveFileLock&& other) - { - if (this == &other) return *this; - - clear(); - fs_ = other.fs_; - handle_ = std::exchange(other.handle_, fs::SystemHandle{}); - return *this; - } + ExclusiveFileLock(ExclusiveFileLock&&) = delete; + ExclusiveFileLock& operator=(ExclusiveFileLock&&) = delete; - ExclusiveFileLock(Wait wait, Filesystem& fs, const fs::path& path_, std::error_code& ec) : fs_(&fs) + ExclusiveFileLock(Wait wait, Filesystem& fs, const fs::path& path_, std::error_code& ec) : m_fs(&fs) { switch (wait) { - case Wait::Yes: handle_ = fs_->take_exclusive_file_lock(path_, ec); break; - case Wait::No: handle_ = fs_->try_take_exclusive_file_lock(path_, ec); break; + case Wait::Yes: m_handle = m_fs->take_exclusive_file_lock(path_, ec); break; + case Wait::No: m_handle = m_fs->try_take_exclusive_file_lock(path_, ec); break; } } ~ExclusiveFileLock() { clear(); } - explicit operator bool() const { return handle_.is_valid(); } - bool has_lock() const { return handle_.is_valid(); } + explicit operator bool() const { return m_handle.is_valid(); } + bool has_lock() const { return m_handle.is_valid(); } void clear() { - if (fs_ && handle_.is_valid()) + if (m_fs && m_handle.is_valid()) { std::error_code ignore; - fs_->unlock_file_lock(std::exchange(handle_, fs::SystemHandle{}), ignore); + m_fs->unlock_file_lock(std::exchange(m_handle, fs::SystemHandle{}), ignore); } } private: - fs::SystemHandle handle_; - Filesystem* fs_; + Filesystem* m_fs; + fs::SystemHandle m_handle; }; } diff --git a/include/vcpkg/base/json.h b/include/vcpkg/base/json.h index e7f3076f78d319..31696b757eb5a3 100644 --- a/include/vcpkg/base/json.h +++ b/include/vcpkg/base/json.h @@ -291,7 +291,9 @@ namespace vcpkg::Json ExpectedT, std::unique_ptr> parse_file( const Files::Filesystem&, const fs::path&, std::error_code& ec) noexcept; ExpectedT, std::unique_ptr> parse( - StringView text, const fs::path& filepath = {}) noexcept; + StringView text, const fs::path& filepath) noexcept; + ExpectedT, std::unique_ptr> parse(StringView text, + StringView origin = {}) noexcept; std::pair parse_file(vcpkg::LineInfo linfo, const Files::Filesystem&, const fs::path&) noexcept; std::string stringify(const Value&, JsonStyle style); diff --git a/include/vcpkg/base/jsonreader.h b/include/vcpkg/base/jsonreader.h index da086fa028cf9f..4aca02998a9af2 100644 --- a/include/vcpkg/base/jsonreader.h +++ b/include/vcpkg/base/jsonreader.h @@ -278,7 +278,7 @@ namespace vcpkg::Json struct NaturalNumberDeserializer final : IDeserializer { - virtual StringView type_name() const override { return "a natural number"; } + virtual StringView type_name() const override { return "a nonnegative integer"; } virtual Optional visit_integer(Reader&, int64_t value) override { diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h index f2dd0798b9df3d..52ce5398083b9b 100644 --- a/include/vcpkg/sourceparagraph.h +++ b/include/vcpkg/sourceparagraph.h @@ -69,6 +69,7 @@ namespace vcpkg std::vector overrides; std::vector default_features; std::string license; // SPDX license expression + Optional builtin_baseline; Type type; PlatformExpression::Expr supports_expression; diff --git a/include/vcpkg/vcpkgpaths.h b/include/vcpkg/vcpkgpaths.h index 36447b9f37b69a..d36ebfb7cff2b4 100644 --- a/include/vcpkg/vcpkgpaths.h +++ b/include/vcpkg/vcpkgpaths.h @@ -118,8 +118,12 @@ namespace vcpkg const std::string& get_tool_version(const std::string& tool) const; // Git manipulation in the vcpkg directory - fs::path git_checkout_baseline(Files::Filesystem& filesystem, StringView commit_sha) const; - fs::path git_checkout_port(Files::Filesystem& filesystem, StringView port_name, StringView git_tree) const; + ExpectedS get_current_git_sha() const; + std::string get_current_git_sha_message() const; + ExpectedS git_checkout_baseline(StringView commit_sha) const; + ExpectedS git_checkout_port(StringView port_name, + StringView git_tree, + const fs::path& dot_git_dir) const; ExpectedS git_show(const std::string& treeish, const fs::path& dot_git_dir) const; ExpectedS>> git_get_local_port_treeish_map() const; @@ -168,12 +172,5 @@ namespace vcpkg const fs::path& destination, const fs::path& dot_git_dir, const fs::path& work_tree); - - static void 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); }; } diff --git a/include/vcpkg/versions.h b/include/vcpkg/versions.h index 19b5546eaf2c10..b26c90dfbaf6be 100644 --- a/include/vcpkg/versions.h +++ b/include/vcpkg/versions.h @@ -81,8 +81,7 @@ namespace vcpkg::Versions enum class Type { None, - Minimum, - Exact + Minimum }; }; } diff --git a/src/vcpkg-test/dependencies.cpp b/src/vcpkg-test/dependencies.cpp index 2831ea5a1abb2c..8f936abc38e17f 100644 --- a/src/vcpkg-test/dependencies.cpp +++ b/src/vcpkg-test/dependencies.cpp @@ -196,7 +196,7 @@ struct MockOverlayProvider : PortFileProvider::IOverlayProvider, Util::ResourceB static const MockOverlayProvider s_empty_mock_overlay; -ExpectedS create_versioned_install_plan( +static ExpectedS create_versioned_install_plan( const PortFileProvider::IVersionedPortfileProvider& provider, const PortFileProvider::IBaselineProvider& bprovider, const CMakeVars::CMakeVarProvider& var_provider, @@ -335,7 +335,7 @@ TEST_CASE ("basic version install scheme baseline missing success", "[versionpla bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "2"}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2"}}, }, {}, toplevel_spec())); @@ -375,7 +375,7 @@ TEST_CASE ("version string baseline agree", "[versionplan]") MockCMakeVarProvider var_provider; auto install_plan = create_versioned_install_plan( - vp, bp, var_provider, {Dependency{"a", {}, {}, {Constraint::Type::Exact, "2"}}}, {}, toplevel_spec()); + vp, bp, var_provider, {Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2"}}}, {}, toplevel_spec()); REQUIRE(install_plan.has_value()); } @@ -396,7 +396,7 @@ TEST_CASE ("version install scheme baseline conflict", "[versionplan]") bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "3"}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "3"}}, }, {}, toplevel_spec()); @@ -421,7 +421,7 @@ TEST_CASE ("version install string port version", "[versionplan]") bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "2", 1}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2", 1}}, }, {}, toplevel_spec())); @@ -447,7 +447,7 @@ TEST_CASE ("version install string port version 2", "[versionplan]") bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "2", 0}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2", 0}}, }, {}, toplevel_spec())); @@ -463,10 +463,10 @@ TEST_CASE ("version install transitive string", "[versionplan]") MockVersionedPortfileProvider vp; vp.emplace("a", {"2", 0}).source_control_file->core_paragraph->dependencies = { - Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Exact, "1"}}, + Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "1"}}, }; vp.emplace("a", {"2", 1}).source_control_file->core_paragraph->dependencies = { - Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Exact, "2"}}, + Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "2"}}, }; vp.emplace("b", {"1", 0}); vp.emplace("b", {"2", 0}); @@ -478,7 +478,7 @@ TEST_CASE ("version install transitive string", "[versionplan]") bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "2", 1}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2", 1}}, }, {}, toplevel_spec())); @@ -1006,7 +1006,7 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") { MockVersionedPortfileProvider vp; vp.emplace("a", {"2", 0}).source_control_file->core_paragraph->dependencies = { - Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Exact, "1"}}, + Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "1"}}, }; vp.emplace("a", {"2", 1}).source_control_file->core_paragraph->dependencies = { Dependency{"b", {}, {}, DependencyConstraint{Constraint::Type::Minimum, "1", 1}}, @@ -1026,7 +1026,7 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "2", 1}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2", 1}}, }, {}, toplevel_spec())); @@ -1045,7 +1045,7 @@ TEST_CASE ("version install scheme change in port version", "[versionplan]") bp, var_provider, { - Dependency{"a", {}, {}, {Constraint::Type::Exact, "2", 0}}, + Dependency{"a", {}, {}, {Constraint::Type::Minimum, "2", 0}}, }, {}, toplevel_spec())); @@ -1305,7 +1305,8 @@ TEST_CASE ("version install transitive overrides", "[versionplan]") MockVersionedPortfileProvider vp; vp.emplace("b", {"1", 0}, Scheme::Relaxed) - .source_control_file->core_paragraph->dependencies.push_back({"c", {}, {}, {Constraint::Type::Exact, "2", 1}}); + .source_control_file->core_paragraph->dependencies.push_back( + {"c", {}, {}, {Constraint::Type::Minimum, "2", 1}}); vp.emplace("b", {"2", 0}, Scheme::Relaxed); vp.emplace("c", {"1", 0}, Scheme::String); vp.emplace("c", {"2", 1}, Scheme::String); diff --git a/src/vcpkg-test/manifests.cpp b/src/vcpkg-test/manifests.cpp index 39e4700750dcc8..0af9f4f968a409 100644 --- a/src/vcpkg-test/manifests.cpp +++ b/src/vcpkg-test/manifests.cpp @@ -62,6 +62,7 @@ TEST_CASE ("manifest construct minimum", "[manifests]") REQUIRE(pgh.core_paragraph->maintainers.empty()); REQUIRE(pgh.core_paragraph->description.empty()); REQUIRE(pgh.core_paragraph->dependencies.empty()); + REQUIRE(!pgh.core_paragraph->builtin_baseline.has_value()); REQUIRE(!pgh.check_against_feature_flags({}, feature_flags_without_versioning)); } @@ -157,19 +158,31 @@ TEST_CASE ("manifest versioning", "[manifests]") } } -TEST_CASE ("manifest constraints error hash", "[manifests]") +TEST_CASE ("manifest constraints hash", "[manifests]") { + auto p = unwrap(test_parse_manifest(R"json({ + "name": "zlib", + "version-string": "abcd", + "dependencies": [ + { + "name": "d", + "version>=": "2018-09-01#1" + } + ] +})json")); + REQUIRE(p->core_paragraph->dependencies.at(0).constraint.value == "2018-09-01"); + REQUIRE(p->core_paragraph->dependencies.at(0).constraint.port_version == 1); + test_parse_manifest(R"json({ "name": "zlib", "version-string": "abcd", "dependencies": [ { - "name": "b", - "version=": "5#1" + "name": "d", + "version>=": "2018-09-01#0" } ] -} -)json", +})json", true); test_parse_manifest(R"json({ @@ -178,7 +191,20 @@ TEST_CASE ("manifest constraints error hash", "[manifests]") "dependencies": [ { "name": "d", - "version>=": "2018-09-01#1" + "version>=": "2018-09-01#-1" + } + ] +})json", + true); + + test_parse_manifest(R"json({ + "name": "zlib", + "version-string": "abcd", + "dependencies": [ + { + "name": "d", + "version>=": "2018-09-01", + "port-version": 1 } ] })json", @@ -238,13 +264,9 @@ TEST_CASE ("manifest constraints", "[manifests]") std::string raw = R"json({ "name": "zlib", "version-string": "abcd", + "builtin-baseline": "089fa4de7dca22c67dcab631f618d5cd0697c8d4", "dependencies": [ "a", - { - "name": "b", - "port-version": 12, - "version=": "5" - }, { "$extra": null, "name": "c" @@ -263,32 +285,17 @@ TEST_CASE ("manifest constraints", "[manifests]") REQUIRE(pgh.check_against_feature_flags({}, feature_flags_without_versioning)); REQUIRE(!pgh.check_against_feature_flags({}, feature_flags_with_versioning)); REQUIRE(Json::stringify(serialize_manifest(pgh), Json::JsonStyle::with_spaces(4)) == raw); - REQUIRE(pgh.core_paragraph->dependencies.size() == 4); + REQUIRE(pgh.core_paragraph->dependencies.size() == 3); REQUIRE(pgh.core_paragraph->dependencies[0].name == "a"); REQUIRE(pgh.core_paragraph->dependencies[0].constraint == DependencyConstraint{Versions::Constraint::Type::None, "", 0}); - REQUIRE(pgh.core_paragraph->dependencies[1].name == "b"); + REQUIRE(pgh.core_paragraph->dependencies[1].name == "c"); REQUIRE(pgh.core_paragraph->dependencies[1].constraint == - DependencyConstraint{Versions::Constraint::Type::Exact, "5", 12}); - REQUIRE(pgh.core_paragraph->dependencies[2].name == "c"); - REQUIRE(pgh.core_paragraph->dependencies[2].constraint == DependencyConstraint{Versions::Constraint::Type::None, "", 0}); - REQUIRE(pgh.core_paragraph->dependencies[3].name == "d"); - REQUIRE(pgh.core_paragraph->dependencies[3].constraint == + REQUIRE(pgh.core_paragraph->dependencies[2].name == "d"); + REQUIRE(pgh.core_paragraph->dependencies[2].constraint == DependencyConstraint{Versions::Constraint::Type::Minimum, "2018-09-01", 0}); - - test_parse_manifest(R"json({ - "name": "zlib", - "version-string": "abcd", - "dependencies": [ - { - "name": "d", - "version=": "2018-09-01", - "version>=": "2018-09-01" - } - ] - })json", - true); + REQUIRE(pgh.core_paragraph->builtin_baseline == "089fa4de7dca22c67dcab631f618d5cd0697c8d4"); test_parse_manifest(R"json({ "name": "zlib", @@ -303,6 +310,45 @@ TEST_CASE ("manifest constraints", "[manifests]") true); } +TEST_CASE ("manifest builtin-baseline", "[manifests]") +{ + SECTION ("valid baseline") + { + std::string raw = R"json({ + "name": "zlib", + "version-string": "abcd", + "builtin-baseline": "089fa4de7dca22c67dcab631f618d5cd0697c8d4" +} +)json"; + auto m_pgh = test_parse_manifest(raw); + + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + REQUIRE(pgh.check_against_feature_flags({}, feature_flags_without_versioning)); + REQUIRE(!pgh.check_against_feature_flags({}, feature_flags_with_versioning)); + REQUIRE(pgh.core_paragraph->builtin_baseline.value_or("does not have a value") == + "089fa4de7dca22c67dcab631f618d5cd0697c8d4"); + } + + SECTION ("empty baseline") + { + std::string raw = R"json({ + "name": "zlib", + "version-string": "abcd", + "builtin-baseline": "" +} +)json"; + + auto m_pgh = test_parse_manifest(raw); + + REQUIRE(m_pgh.has_value()); + auto& pgh = **m_pgh.get(); + REQUIRE(pgh.check_against_feature_flags({}, feature_flags_without_versioning)); + REQUIRE(!pgh.check_against_feature_flags({}, feature_flags_with_versioning)); + REQUIRE(pgh.core_paragraph->builtin_baseline.value_or("does not have a value") == ""); + } +} + TEST_CASE ("manifest overrides", "[manifests]") { std::tuple data[] = { diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp index 4a784235e089f2..d99fa73925d9b5 100644 --- a/src/vcpkg/base/json.cpp +++ b/src/vcpkg/base/json.cpp @@ -1064,6 +1064,11 @@ namespace vcpkg::Json { return Parser::parse(json, fs::generic_u8string(filepath)); } + ExpectedT, std::unique_ptr> parse(StringView json, + StringView origin) noexcept + { + return Parser::parse(json, origin); + } // } auto parse() namespace diff --git a/src/vcpkg/commands.civerifyversions.cpp b/src/vcpkg/commands.civerifyversions.cpp index 84aab9c019dd86..71ced9f76a477d 100644 --- a/src/vcpkg/commands.civerifyversions.cpp +++ b/src/vcpkg/commands.civerifyversions.cpp @@ -404,7 +404,7 @@ namespace vcpkg::Commands::CIVerifyVersions System::printf(System::Color::error, "%s\n", error); } System::print2(System::Color::error, - "\nTo attempt to resolve all erros at once, run:\n\n" + "\nTo attempt to resolve all errors at once, run:\n\n" " vcpkg x-add-version --all\n\n"); Checks::exit_fail(VCPKG_LINE_INFO); } diff --git a/src/vcpkg/help.cpp b/src/vcpkg/help.cpp index ded6592fbe90be..0556b4fa064e03 100644 --- a/src/vcpkg/help.cpp +++ b/src/vcpkg/help.cpp @@ -47,7 +47,69 @@ namespace vcpkg::Help nullptr, }; - static constexpr std::array topics = {{ + static void help_topic_versioning(const VcpkgPaths&) + { + HelpTableFormatter tbl; + tbl.text("Versioning allows you to deterministically control the precise revisions of dependencies used by " + "your project from within your manifest file."); + tbl.blank(); + tbl.blank(); + tbl.text("** This feature is experimental and requires `--feature-flags=versions` **"); + tbl.blank(); + tbl.blank(); + tbl.header("Versions in vcpkg come in four primary flavors"); + tbl.format("version", "A dot-separated sequence of numbers (1.2.3.4)"); + tbl.format("version-date", "A date (2021-01-01.5)"); + tbl.format("version-semver", "A Semantic Version 2.0 (2.1.0-rc2)"); + tbl.format("version-string", "An exact, incomparable version (Vista)"); + tbl.blank(); + tbl.text("Each version additionally has a \"port-version\" which is a nonnegative integer. When rendered as " + "text, the port version (if nonzero) is added as a suffix to the primary version text separated by a " + "hash (#). Port-versions are sorted lexographically after the primary version text, for example:"); + tbl.blank(); + tbl.blank(); + tbl.text(" 1.0.0 < 1.0.0#1 < 1.0.1 < 1.0.1#5 < 2.0.0"); + tbl.blank(); + tbl.blank(); + tbl.header("Manifests can place three kinds of constraints upon the versions used"); + tbl.format("builtin-baseline", + "The baseline references a commit within the vcpkg repository that establishes a minimum version on " + "every dependency in the graph. If no other constraints are specified (directly or transitively), " + "then the version from the baseline of the top level manifest will be used. Baselines of transitive " + "dependencies are ignored."); + tbl.blank(); + tbl.format("version>=", + "Within the \"dependencies\" field, each dependency can have a minimum constraint listed. These " + "minimum constraints will be used when transitively depending upon this library. A minimum " + "port-version can additionally be specified with a '#' suffix."); + tbl.blank(); + tbl.format( + "overrides", + "When used as the top-level manifest (such as when running `vcpkg install` in the directory), overrides " + "allow a manifest to short-circuit dependency resolution and specify exactly the version to use. These can " + "be used to handle version conflicts, such as with `version-string` dependencies. They will not be " + "considered when transitively depended upon."); + tbl.blank(); + tbl.text("Example manifest:"); + tbl.blank(); + tbl.text(R"({ + "name": "example", + "version": "1.0", + "builtin-baseline": "a14a6bcb27287e3ec138dba1b948a0cdbc337a3a", + "dependencies": [ + { "name": "zlib", "version>=": "1.2.11#8" }, + "rapidjson" + ], + "overrides": [ + { "name": "rapidjson", "version": "2020-09-14" } + ] +})"); + System::print2(tbl.m_str, + "\nExtended documentation is available at " + "https://github.com/Microsoft/vcpkg/tree/master/docs/users/versioning.md\n"); + } + + static constexpr std::array topics = {{ {"binarycaching", help_topic_binary_caching}, {"create", command_topic_fn}, {"depend-info", command_topic_fn}, @@ -63,6 +125,7 @@ namespace vcpkg::Help {"search", command_topic_fn}, {"topics", help_topics}, {"triplet", help_topic_valid_triplet}, + {"versioning", help_topic_versioning}, }}; static void help_topics(const VcpkgPaths&) diff --git a/src/vcpkg/install.cpp b/src/vcpkg/install.cpp index 80b4eae3ae9a4c..e86a78a764b464 100644 --- a/src/vcpkg/install.cpp +++ b/src/vcpkg/install.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -847,10 +848,25 @@ namespace vcpkg::Install if (args.versions_enabled() || args.registries_enabled()) { - if (auto p_baseline = manifest_scf.core_paragraph->extra_info.get("$x-default-baseline")) + if (!manifest_scf.core_paragraph->overrides.empty()) { - paths.get_configuration().registry_set.experimental_set_builtin_registry_baseline( - p_baseline->string()); + Metrics::g_metrics.lock()->track_property("manifest_overrides", "defined"); + } + if (auto p_baseline = manifest_scf.core_paragraph->builtin_baseline.get()) + { + Metrics::g_metrics.lock()->track_property("manifest_baseline", "defined"); + if (p_baseline->size() != 40 || !std::all_of(p_baseline->begin(), p_baseline->end(), [](char ch) { + return (ch >= 'a' || ch <= 'f') || Parse::ParserBase::is_ascii_digit(ch); + })) + { + Checks::exit_with_message( + VCPKG_LINE_INFO, + "Error: the top-level builtin-baseline (%s) was not a valid commit sha: " + "expected 40 lowercase hexadecimal characters.\n%s\n", + *p_baseline, + paths.get_current_git_sha_message()); + } + paths.get_configuration().registry_set.experimental_set_builtin_registry_baseline(*p_baseline); } } if (args.versions_enabled()) diff --git a/src/vcpkg/paragraphs.cpp b/src/vcpkg/paragraphs.cpp index 32103c76b9e6f6..2d28c49d705d6c 100644 --- a/src/vcpkg/paragraphs.cpp +++ b/src/vcpkg/paragraphs.cpp @@ -373,7 +373,14 @@ namespace vcpkg::Paragraphs auto error_info = std::make_unique(); error_info->name = fs::u8string(path.filename()); - error_info->error = "Failed to find either a CONTROL file or vcpkg.json file."; + if (fs.exists(path)) + { + error_info->error = "Failed to find either a CONTROL file or vcpkg.json file."; + } + else + { + error_info->error = "The port directory (" + fs::u8string(path) + ") does not exist"; + } return error_info; } diff --git a/src/vcpkg/registries.cpp b/src/vcpkg/registries.cpp index b9afa089bef1b1..934e6537a095d5 100644 --- a/src/vcpkg/registries.cpp +++ b/src/vcpkg/registries.cpp @@ -186,17 +186,6 @@ namespace DelayedInit m_baseline; }; - ExpectedS get_git_baseline_json_path(const VcpkgPaths& paths, StringView baseline_commit_sha) - { - auto baseline_path = paths.git_checkout_baseline(paths.get_filesystem(), baseline_commit_sha); - if (paths.get_filesystem().exists(baseline_path)) - { - return std::move(baseline_path); - } - return {Strings::concat("Error: Baseline database file does not exist: ", fs::u8string(baseline_path)), - expected_right_tag}; - } - struct VersionDbEntry { VersionT version; @@ -224,7 +213,7 @@ namespace // returns nullopt if the baseline is valid, but doesn't contain the specified baseline, // or (equivalently) if the baseline does not exist. - ExpectedS> parse_baseline_versions(StringView contents, StringView baseline); + ExpectedS> parse_baseline_versions(StringView contents, StringView baseline, StringView origin); ExpectedS> load_baseline_versions(const VcpkgPaths& paths, const fs::path& path_to_baseline, StringView identifier = {}); @@ -327,18 +316,20 @@ namespace if (baseline_identifier == "default") { - return Strings::format("Couldn't find explicitly specified baseline `\"default\"` in baseline file: %s", - fs::u8string(path_to_baseline)); + return Strings::format( + "Error: Couldn't find explicitly specified baseline `\"default\"` in baseline file: %s", + fs::u8string(path_to_baseline)); } // attempt to check out the baseline: - auto maybe_path = get_git_baseline_json_path(paths, baseline_identifier); + auto maybe_path = paths.git_checkout_baseline(baseline_identifier); if (!maybe_path.has_value()) { - return Strings::format("Couldn't find explicitly specified baseline `\"%s\"` in the baseline file, " - "and there was no baseline at that commit or the commit didn't exist.\n%s", + return Strings::format("Error: Couldn't find explicitly specified baseline `\"%s\"` in the baseline file, " + "and there was no baseline at that commit or the commit didn't exist.\n%s\n%s", baseline_identifier, - maybe_path.error()); + maybe_path.error(), + paths.get_current_git_sha_message()); } res_baseline = load_baseline_versions(paths, *maybe_path.get()); @@ -352,7 +343,7 @@ namespace return std::move(*p); } - return Strings::format("Couldn't find explicitly specified baseline `\"%s\"` in the baseline " + return Strings::format("Error: Couldn't find explicitly specified baseline `\"%s\"` in the baseline " "file, and the `\"default\"` baseline does not exist at that commit.", baseline_identifier); } @@ -549,7 +540,7 @@ namespace } auto contents = maybe_contents.get(); - res_baseline = parse_baseline_versions(*contents, {}); + res_baseline = parse_baseline_versions(*contents, "default", fs::u8string(path_to_baseline)); if (!res_baseline.has_value()) { Checks::exit_with_message(VCPKG_LINE_INFO, res_baseline.error()); @@ -599,12 +590,22 @@ namespace auto it = std::find(git_entry->port_versions.begin(), git_entry->port_versions.end(), version); if (it == git_entry->port_versions.end()) { - return Strings::concat( - "Error: No version entry for ", git_entry->port_name, " at version ", version, "."); + return { + Strings::concat("Error: No version entry for ", + git_entry->port_name, + " at version ", + version, + ". This may be fixed by updating vcpkg to the latest master via `git " + "pull`.\nAvailable versions:\n", + Strings::join("", + git_entry->port_versions, + [](const VersionT& v) { return Strings::concat(" ", v, "\n"); }), + "\nSee `vcpkg help versioning` for more information."), + expected_right_tag}; } const auto& git_tree = git_entry->git_trees[it - git_entry->port_versions.begin()]; - return paths.git_checkout_port(paths.get_filesystem(), git_entry->port_name, git_tree); + return paths.git_checkout_port(git_entry->port_name, git_tree, paths.root / fs::u8path(".git")); } if (scfl_version == version) @@ -1014,19 +1015,20 @@ namespace return db_entries; } - ExpectedS> parse_baseline_versions(StringView contents, StringView baseline) + ExpectedS> parse_baseline_versions(StringView contents, StringView baseline, StringView origin) { - auto maybe_value = Json::parse(contents); + auto maybe_value = Json::parse(contents, origin); if (!maybe_value.has_value()) { - return Strings::format("Error: failed to parse baseline file: %s", maybe_value.error()->format()); + return Strings::format( + "Error: failed to parse baseline file: %s\n%s", origin, maybe_value.error()->format()); } auto& value = *maybe_value.get(); if (!value.first.is_object()) { - return std::string("Error: baseline does not have a top-level object."); + return Strings::concat("Error: baseline does not have a top-level object: ", origin); } auto real_baseline = baseline.size() == 0 ? "default" : baseline; @@ -1047,8 +1049,7 @@ namespace } else { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: failed to parse baseline:\n%s", Strings::join("\n", r.errors())); + return Strings::format("Error: failed to parse baseline: %s\n%s", origin, Strings::join("\n", r.errors())); } } @@ -1059,7 +1060,7 @@ namespace auto maybe_contents = paths.get_filesystem().read_contents(path_to_baseline); if (auto contents = maybe_contents.get()) { - return parse_baseline_versions(*contents, baseline); + return parse_baseline_versions(*contents, baseline, fs::u8string(path_to_baseline)); } else if (maybe_contents.error() == std::errc::no_such_file_or_directory) { @@ -1143,7 +1144,7 @@ namespace vcpkg if (!default_registry_is_builtin || registries_.size() != 0) { System::print2(System::Color::warning, - "Warning: when using the registries feature, one should not use `\"$x-default-baseline\"` " + "Warning: when using the registries feature, one should not use `\"builtin-baseline\"` " "to set the baseline.\n", " Instead, use the \"baseline\" field of the registry.\n"); } diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index c4ae3e85569eeb..61cc7f8264d542 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -430,8 +430,6 @@ namespace vcpkg constexpr static StringLiteral FEATURES = "features"; constexpr static StringLiteral DEFAULT_FEATURES = "default-features"; constexpr static StringLiteral PLATFORM = "platform"; - constexpr static StringLiteral PORT_VERSION = "port-version"; - constexpr static StringLiteral VERSION_EQ = "version="; constexpr static StringLiteral VERSION_GE = "version>="; virtual Span valid_fields() const override @@ -441,8 +439,6 @@ namespace vcpkg FEATURES, DEFAULT_FEATURES, PLATFORM, - PORT_VERSION, - VERSION_EQ, VERSION_GE, }; @@ -488,33 +484,34 @@ namespace vcpkg r.optional_object_field(obj, PLATFORM, dep.platform, PlatformExprDeserializer::instance); - static auto version_deserializer = make_version_deserializer("a version"); + static Json::StringDeserializer version_deserializer("a version"); - auto has_eq_constraint = - r.optional_object_field(obj, VERSION_EQ, dep.constraint.value, *version_deserializer); auto has_ge_constraint = - r.optional_object_field(obj, VERSION_GE, dep.constraint.value, *version_deserializer); - auto has_port_ver = r.optional_object_field( - obj, PORT_VERSION, dep.constraint.port_version, Json::NaturalNumberDeserializer::instance); + r.optional_object_field(obj, VERSION_GE, dep.constraint.value, version_deserializer); - if (has_eq_constraint) + if (has_ge_constraint) { - dep.constraint.type = Versions::Constraint::Type::Exact; - if (has_ge_constraint) + dep.constraint.type = Versions::Constraint::Type::Minimum; + auto h = dep.constraint.value.find('#'); + if (h != std::string::npos) { - r.add_generic_error(type_name(), "cannot have both exact and minimum constraints simultaneously"); + auto opt = Strings::strto(dep.constraint.value.c_str() + h + 1); + auto v = opt.get(); + if (v && *v > 0) + { + dep.constraint.port_version = *v; + } + else + { + r.add_generic_error(type_name(), + "embedded port-version ('#') in the primary " + "constraint (\"", + VERSION_GE, + "\") must be a positive integer"); + } + dep.constraint.value.erase(h); } } - else if (has_ge_constraint) - { - dep.constraint.type = Versions::Constraint::Type::Minimum; - } - else if (has_port_ver) // does not have a primary constraint - { - r.add_generic_error( - type_name(), - "\"port-version\" cannot be used without a primary constraint (\"version=\" or \"version>=\")"); - } return dep; } @@ -540,9 +537,7 @@ namespace vcpkg constexpr StringLiteral DependencyDeserializer::FEATURES; constexpr StringLiteral DependencyDeserializer::DEFAULT_FEATURES; constexpr StringLiteral DependencyDeserializer::PLATFORM; - constexpr StringLiteral DependencyDeserializer::VERSION_EQ; constexpr StringLiteral DependencyDeserializer::VERSION_GE; - constexpr StringLiteral DependencyDeserializer::PORT_VERSION; struct DependencyOverrideDeserializer : Json::IDeserializer { @@ -860,6 +855,21 @@ namespace vcpkg }; LicenseExpressionDeserializer LicenseExpressionDeserializer::instance; + struct BaselineCommitDeserializer final : Json::IDeserializer + { + virtual StringView type_name() const override { return "a vcpkg repository commit"; } + + virtual Optional visit_string(Json::Reader&, StringView s) override + { + // We allow non-sha strings here to allow the core vcpkg code to provide better error + // messages including the current git commit + return s.to_string(); + } + + static BaselineCommitDeserializer instance; + }; + BaselineCommitDeserializer BaselineCommitDeserializer::instance; + struct ManifestDeserializer : Json::IDeserializer> { virtual StringView type_name() const override { return "a manifest"; } @@ -876,6 +886,7 @@ namespace vcpkg constexpr static StringLiteral DEFAULT_FEATURES = "default-features"; constexpr static StringLiteral SUPPORTS = "supports"; constexpr static StringLiteral OVERRIDES = "overrides"; + constexpr static StringLiteral BUILTIN_BASELINE = "builtin-baseline"; virtual Span valid_fields() const override { @@ -892,6 +903,7 @@ namespace vcpkg DEFAULT_FEATURES, SUPPORTS, OVERRIDES, + BUILTIN_BASELINE, }; static const auto t = Util::Vectors::concat(schemed_deserializer_fields(), u); @@ -917,9 +929,9 @@ namespace vcpkg static Json::StringDeserializer url_deserializer{"a url"}; - constexpr static StringView type_name = "vcpkg.json"; + constexpr static StringView inner_type_name = "vcpkg.json"; DependencyOverrideDeserializer::visit_impl( - type_name, r, obj, spgh->name, spgh->version, spgh->version_scheme, spgh->port_version); + inner_type_name, r, obj, spgh->name, spgh->version, spgh->version_scheme, spgh->port_version); r.optional_object_field(obj, MAINTAINERS, spgh->maintainers, Json::ParagraphDeserializer::instance); r.optional_object_field(obj, DESCRIPTION, spgh->description, Json::ParagraphDeserializer::instance); @@ -933,8 +945,12 @@ namespace vcpkg if (obj.contains(DEV_DEPENDENCIES)) { - System::print2(System::Color::error, DEV_DEPENDENCIES, " are not yet supported"); - Checks::exit_fail(VCPKG_LINE_INFO); + r.add_generic_error(type_name(), DEV_DEPENDENCIES, " are not yet supported"); + } + std::string baseline; + if (r.optional_object_field(obj, BUILTIN_BASELINE, baseline, BaselineCommitDeserializer::instance)) + { + spgh->builtin_baseline = std::move(baseline); } r.optional_object_field(obj, SUPPORTS, spgh->supports_expression, PlatformExprDeserializer::instance); @@ -1007,6 +1023,7 @@ namespace vcpkg Optional SourceControlFile::check_against_feature_flags(const fs::path& origin, const FeatureFlagSettings& flags) const { + static constexpr StringLiteral s_extended_help = "See `vcpkg help versioning` for more information."; if (!flags.versions) { auto check_deps = [&](View deps) -> Optional { @@ -1014,11 +1031,12 @@ namespace vcpkg { if (dep.constraint.type != Versions::Constraint::Type::None) { - return Strings::concat(fs::u8string(origin), - " was rejected because it uses constraints and the `", - VcpkgCmdArguments::VERSIONS_FEATURE, - "` feature flag is disabled.\nThis can be fixed by removing uses of " - "\"version>=\" and \"version=\"."); + return Strings::concat( + fs::u8string(origin), + " was rejected because it uses constraints and the `", + VcpkgCmdArguments::VERSIONS_FEATURE, + "` feature flag is disabled.\nThis can be fixed by removing uses of \"version>=\".\n", + s_extended_help); } } return nullopt; @@ -1036,7 +1054,18 @@ namespace vcpkg return Strings::concat(fs::u8string(origin), " was rejected because it uses overrides and the `", VcpkgCmdArguments::VERSIONS_FEATURE, - "` feature flag is disabled.\nThis can be fixed by removing \"overrides\"."); + "` feature flag is disabled.\nThis can be fixed by removing \"overrides\".\n", + s_extended_help); + } + + if (core_paragraph->builtin_baseline.has_value()) + { + return Strings::concat( + fs::u8string(origin), + " was rejected because it uses builtin-baseline and the `", + VcpkgCmdArguments::VERSIONS_FEATURE, + "` feature flag is disabled.\nThis can be fixed by removing \"builtin-baseline\".\n", + s_extended_help); } } return nullopt; @@ -1242,19 +1271,14 @@ namespace vcpkg serialize_optional_array(dep_obj, DependencyDeserializer::FEATURES, features_copy); serialize_optional_string(dep_obj, DependencyDeserializer::PLATFORM, to_string(dep.platform)); - if (dep.constraint.port_version != 0) - { - dep_obj.insert(DependencyDeserializer::PORT_VERSION, - Json::Value::integer(dep.constraint.port_version)); - } - - if (dep.constraint.type == Versions::Constraint::Type::Exact) - { - dep_obj.insert(DependencyDeserializer::VERSION_EQ, Json::Value::string(dep.constraint.value)); - } - else if (dep.constraint.type == Versions::Constraint::Type::Minimum) + if (dep.constraint.type == Versions::Constraint::Type::Minimum) { - dep_obj.insert(DependencyDeserializer::VERSION_GE, Json::Value::string(dep.constraint.value)); + auto s = dep.constraint.value; + if (dep.constraint.port_version != 0) + { + Strings::append(s, '#', dep.constraint.port_version); + } + dep_obj.insert(DependencyDeserializer::VERSION_GE, Json::Value::string(std::move(s))); } } }; @@ -1294,6 +1318,11 @@ namespace vcpkg serialize_optional_string(obj, ManifestDeserializer::LICENSE, scf.core_paragraph->license); serialize_optional_string( obj, ManifestDeserializer::SUPPORTS, to_string(scf.core_paragraph->supports_expression)); + if (scf.core_paragraph->builtin_baseline.has_value()) + { + obj.insert(ManifestDeserializer::BUILTIN_BASELINE, + Json::Value::string(scf.core_paragraph->builtin_baseline.value_or_exit(VCPKG_LINE_INFO))); + } if (!scf.core_paragraph->dependencies.empty() || debug) { diff --git a/src/vcpkg/vcpkgpaths.cpp b/src/vcpkg/vcpkgpaths.cpp index afd7247655f272..7926e7a5fbd09e 100644 --- a/src/vcpkg/vcpkgpaths.cpp +++ b/src/vcpkg/vcpkgpaths.cpp @@ -554,6 +554,33 @@ If you wish to silence this error and use classic mode, you can: } } + ExpectedS VcpkgPaths::get_current_git_sha() const + { + auto cmd = git_cmd_builder(*this, this->root / fs::u8path(".git"), this->root); + cmd.string_arg("rev-parse").string_arg("HEAD"); + auto output = System::cmd_execute_and_capture_output(cmd); + if (output.exit_code != 0) + { + return {std::move(output.output), expected_right_tag}; + } + else + { + return {Strings::trim(std::move(output.output)), expected_left_tag}; + } + } + std::string VcpkgPaths::get_current_git_sha_message() const + { + auto maybe_cur_sha = get_current_git_sha(); + if (auto p_sha = maybe_cur_sha.get()) + { + return Strings::concat("The current commit is \"", *p_sha, '"'); + } + else + { + return Strings::concat("Failed to determine the current commit:\n", maybe_cur_sha.error()); + } + } + ExpectedS VcpkgPaths::git_show(const std::string& treeish, const fs::path& dot_git_dir) const { // All git commands are run with: --git-dir={dot_git_dir} --work-tree={work_tree_temp} @@ -620,119 +647,148 @@ If you wish to silence this error and use classic mode, you can: return ret; } - 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") - .string_arg("--no-hardlinks") - .path_arg(local_repo) - .path_arg(dot_git_dir); - const auto clone_output = System::cmd_execute_and_capture_output(clone_cmd_builder); - 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); - 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); - 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 + ExpectedS VcpkgPaths::git_checkout_baseline(StringView commit_sha) const { + Files::Filesystem& fs = get_filesystem(); const fs::path destination_parent = this->baselines_output / fs::u8path(commit_sha); - const fs::path destination = destination_parent / fs::u8path("baseline.json"); + fs::path destination = destination_parent / fs::u8path("baseline.json"); if (!fs.exists(destination)) { + const fs::path destination_tmp = destination_parent / fs::u8path("baseline.json.tmp"); auto treeish = Strings::concat(commit_sha, ":port_versions/baseline.json"); auto maybe_contents = git_show(treeish, this->root / fs::u8path(".git")); if (auto contents = maybe_contents.get()) { - fs.create_directories(destination_parent, VCPKG_LINE_INFO); - fs.write_contents(destination, *contents, VCPKG_LINE_INFO); + std::error_code ec; + fs.create_directories(destination_parent, ec); + if (ec) + { + return {Strings::format( + "Error: while checking out baseline %s\nError: while creating directories %s: %s", + commit_sha, + fs::u8string(destination_parent), + ec.message()), + expected_right_tag}; + } + fs.write_contents(destination_tmp, *contents, ec); + if (ec) + { + return {Strings::format("Error: while checking out baseline %s\nError: while writing %s: %s", + commit_sha, + fs::u8string(destination_tmp), + ec.message()), + expected_right_tag}; + } + fs.rename(destination_tmp, destination, ec); + if (ec) + { + return {Strings::format("Error: while checking out baseline %s\nError: while renaming %s to %s: %s", + commit_sha, + fs::u8string(destination_tmp), + fs::u8string(destination), + ec.message()), + expected_right_tag}; + } } else { - Checks::exit_with_message( - VCPKG_LINE_INFO, "Error: while checking out baseline '%s':\n%s", treeish, maybe_contents.error()); + return {Strings::format("Error: while checking out baseline '%s':\n%s\nThis may be fixed by updating " + "vcpkg to the latest master via `git pull`.", + treeish, + maybe_contents.error()), + expected_right_tag}; } } return destination; } - fs::path VcpkgPaths::git_checkout_port(Files::Filesystem& fs, StringView port_name, StringView git_tree) const + ExpectedS VcpkgPaths::git_checkout_port(StringView port_name, + StringView git_tree, + const fs::path& dot_git_dir) 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. + /* Check out a git tree into the versioned port recipes folder * * 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; - fs::path destination = this->versions_output / fs::u8path(git_tree) / fs::u8path(port_name); + Files::Filesystem& fs = get_filesystem(); + fs::path destination = this->versions_output / fs::u8path(port_name) / fs::u8path(git_tree); + if (fs.exists(destination)) + { + return destination; + } - if (!fs.exists(destination / "CONTROL") && !fs.exists(destination / "vcpkg.json")) + const fs::path destination_tmp = + this->versions_output / fs::u8path(port_name) / fs::u8path(Strings::concat(git_tree, ".tmp")); + const fs::path destination_tar = + this->versions_output / fs::u8path(port_name) / fs::u8path(Strings::concat(git_tree, ".tar")); +#define PRELUDE "Error: while checking out port ", port_name, " with git tree ", git_tree, "\n" + std::error_code ec; + fs::path failure_point; + fs.remove_all(destination_tmp, ec, failure_point); + if (ec) { - git_checkout_object( - *this, git_tree, local_repo, destination, this->versions_dot_git_dir, this->versions_work_tree); + return {Strings::concat(PRELUDE, "Error: while removing ", fs::u8string(failure_point), ": ", ec.message()), + expected_right_tag}; } + fs.create_directories(destination_tmp, ec); + if (ec) + { + return { + Strings::concat( + PRELUDE, "Error: while creating directories ", fs::u8string(destination_tmp), ": ", ec.message()), + expected_right_tag}; + } + + System::CmdLineBuilder tar_cmd_builder = git_cmd_builder(*this, dot_git_dir, dot_git_dir) + .string_arg("archive") + .string_arg(git_tree) + .string_arg("-o") + .path_arg(destination_tar); + const auto tar_output = System::cmd_execute_and_capture_output(tar_cmd_builder); + if (tar_output.exit_code != 0) + { + return {Strings::concat(PRELUDE, "Error: Failed to tar port directory\n", tar_output.output), + expected_right_tag}; + } + + System::CmdLineBuilder extract_cmd_builder; + extract_cmd_builder.path_arg(this->get_tool_exe(Tools::CMAKE)) + .string_arg("-E") + .string_arg("tar") + .string_arg("xf") + .path_arg(destination_tar); + + const auto extract_output = + System::cmd_execute_and_capture_output(extract_cmd_builder, System::InWorkingDirectory{destination_tmp}); + if (extract_output.exit_code != 0) + { + return {Strings::concat(PRELUDE, "Error: Failed to extract port directory\n", extract_output.output), + expected_right_tag}; + } + fs.remove(destination_tar, ec); + if (ec) + { + return { + Strings::concat(PRELUDE, "Error: while removing ", fs::u8string(destination_tar), ": ", ec.message()), + expected_right_tag}; + } + fs.rename(destination_tmp, destination, ec); + if (ec) + { + return {Strings::concat(PRELUDE, + "Error: while renaming ", + fs::u8string(destination_tmp), + " to ", + fs::u8string(destination), + ": ", + ec.message()), + expected_right_tag}; + } + return destination; +#undef PRELUDE } ExpectedS VcpkgPaths::git_fetch_from_remote_registry(StringView repo, StringView treeish) const @@ -757,7 +813,7 @@ If you wish to silence this error and use classic mode, you can: 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); + Files::ExclusiveFileLock guard(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);