diff --git a/docs/users/manifests.md b/docs/users/manifests.md index 427d336c34..e1bc0c58b6 100644 --- a/docs/users/manifests.md +++ b/docs/users/manifests.md @@ -64,6 +64,12 @@ Additionally, the `"port-version"` field is used by registries of packages, as a way to version "the package gotten from `vcpkg install`" differently from the upstream package version. You shouldn't need to worry about this at all. +#### Additional version fields + +**Experimental behind the `versions` feature flag** + +See [versioning.md](versioning.md#version%20schemes) for additional version types. + ### `"description"` This is where you describe your project. Give it a good description to help in searching for it! @@ -71,6 +77,14 @@ This can be a single string, or it can be an array of strings; in the latter case, the first string is treated as a summary, while the remaining strings are treated as the full description. +### `"builtin-baseline"` + +**Experimental behind the `versions` feature flag** + +This field indicates the commit of vcpkg which provides global minimum version information for your manifest. It is required for top-level manifest files using versioning. + +See also [versioning](versioning.md#builtin-baseline) for more semantic details. + ### `"dependencies"` This field lists all the dependencies you'll need to build your library (as well as any your dependents might need, @@ -131,6 +145,16 @@ The common identifiers are: although one can define their own. +#### `"version>="` + +**Experimental behind the `versions` feature flag** + +A minimum version constraint on the dependency. + +This field specifies the minimum version of the dependency using a '#' suffix to denote port-version if non-zero. + +See also [versioning](versioning.md#constraints) for more semantic details. + #### Example: ```json @@ -151,6 +175,26 @@ although one can define their own. } ``` +### `"overrides"` + +**Experimental behind the `versions` feature flag** + +This field enables version resolution to be ignored for certain dependencies and to use specific versions instead. + +See also [versioning](versioning.md#overrides) for more semantic details. + +#### Example: + +```json +{ + "overrides": [ + { + "name": "arrow", "version": "1.2.3", "port-version": 7 + } + ] +} +``` + ### `"supports"` If your project doesn't support common platforms, you can tell your users this with the `"supports"` field. @@ -180,8 +224,9 @@ and that's the `"default-features"` field, which is an array of feature names. "name": "libdb", "description": [ "An example database library.", - "Optionally uses one of CBOR, JSON, or CSV as a backend." + "Optionally can build with CBOR, JSON, or CSV as backends." ], + "$default-features-explanation": "Users using this library transitively will get all backends automatically", "default-features": [ "cbor", "csv", "json" ], "features": { "cbor": { @@ -189,8 +234,7 @@ and that's the `"default-features"` field, which is an array of feature names. "dependencies": [ { "$explanation": [ - "This is currently how you tell vcpkg that the cbor feature depends on the json feature of this package", - "We're looking into making this easier" + "This is how you tell vcpkg that the cbor feature depends on the json feature of this package" ], "name": "libdb", "default-features": false, diff --git a/docs/users/versioning.md b/docs/users/versioning.md new file mode 100644 index 0000000000..86840422be --- /dev/null +++ b/docs/users/versioning.md @@ -0,0 +1,85 @@ +# Versioning + +**This feature is experimental and requires `--feature-flags=versions`** + +Versioning allows you to deterministically control the precise revisions of dependencies used by +your project from within your manifest file. + +## Version schemes + +### Schemes +Versions in vcpkg come in four primary flavors: + +#### version +A dot-separated sequence of numbers (1.2.3.4) + +#### version-date +A date (2021-01-01.5) + +#### version-semver +A Semantic Version 2.0 (2.1.0-rc2) + +See https://semver.org/ for a full specification. + +#### version-string +An exact, incomparable version (Vista) + +### Port Versions +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: + + 1.0.0 < 1.0.0#1 < 1.0.1 < 1.0.1#5 < 2.0.0 + +## Constraints + +Manifests can place three kinds of constraints upon the versions used: + +### 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. + +You can get the current commit of your vcpkg instance either by adding an empty `"builtin-baseline"` field, installing, and examining the error message or by running `git rev-parse HEAD` in the root of the vcpkg instance. + +Baselines provide stability and ease of development for top-level manifest files. They are not considered from ports consumed as a dependency. If a minimum version constraint is required during transitive version resolution, the port should use `version>=`. + +### 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. + +This constraint must refer to an existing, valid version (including port-version). + +### 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. + +Overrides are not considered from ports consumed as a dependency. + +## Example top-level manifest: +```json +{ + "name": "example", + "version": "1.0", + "builtin-baseline": "a14a6bcb27287e3ec138dba1b948a0cdbc337a3a", + "dependencies": [ + { "name": "zlib", "version>=": "1.2.11#8" }, + "rapidjson" + ], + "overrides": [ + { "name": "rapidjson", "version": "2020-09-14" } + ] +} +``` +See also the [manifest documentation](manifests.md) for more syntax information. + +## Original Specification + +See also the [original specification](https://github.com/vicroms/vcpkg/blob/versioning-spec/docs/specifications/versioning.md) diff --git a/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 b/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 index 777c0901fc..ef7023c852 100644 --- a/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-dir/registries.ps1 @@ -14,12 +14,13 @@ Throw-IfNotFailed Run-Vcpkg install @builtinRegistryArgs --feature-flags=registries 'zlib' Throw-IfFailed -# Test git and filesystem registries +Write-Trace "Test git and filesystem registries" Refresh-TestRoot $filesystemRegistry = "$TestingRoot/filesystem-registry" $gitRegistryUpstream = "$TestingRoot/git-registry-upstream" # build a filesystem registry +Write-Trace "build a filesystem registry" New-Item -Path $filesystemRegistry -ItemType Directory $filesystemRegistry = (Get-Item $filesystemRegistry).FullName @@ -51,6 +52,7 @@ New-Item ` # build a git registry +Write-Trace "build a git registry" New-Item -Path $gitRegistryUpstream -ItemType Directory $gitRegistryUpstream = (Get-Item $gitRegistryUpstream).FullName @@ -82,6 +84,7 @@ finally } # actually test the registries +Write-Trace "actually test the registries" $vcpkgJson = @{ "name" = "manifest-test"; "version-string" = "1.0.0"; @@ -90,37 +93,8 @@ $vcpkgJson = @{ ) } -$manifestDir = "$TestingRoot/builtin-registry-test-manifest-dir" - -New-Item -Path $manifestDir -ItemType Directory -$manifestDir = (Get-Item $manifestDir).FullName - -Push-Location $manifestDir - -try -{ - $vcpkgJsonWithBaseline = $vcpkgJson.Clone() - $vcpkgJsonWithBaseline['$x-default-baseline'] = 'default' - - New-Item -Path 'vcpkg.json' -ItemType File ` - -Value (ConvertTo-Json -Depth 5 -InputObject $vcpkgJson) - - Run-Vcpkg install @builtinRegistryArgs '--feature-flags=registries,manifests' - Throw-IfNotFailed - - New-Item -Path 'vcpkg.json' -ItemType File -Force ` - -Value (ConvertTo-Json -Depth 5 -InputObject $vcpkgJsonWithBaseline) - - Run-Vcpkg install @builtinRegistryArgs '--feature-flags=registries,manifests' - Throw-IfFailed -} -finally -{ - Pop-Location -} - - # test the filesystem registry +Write-Trace "test the filesystem registry" $manifestDir = "$TestingRoot/filesystem-registry-test-manifest-dir" New-Item -Path $manifestDir -ItemType Directory @@ -154,6 +128,7 @@ finally } # test the git registry +Write-Trace "test the git registry" $manifestDir = "$TestingRoot/git-registry-test-manifest-dir" New-Item -Path $manifestDir -ItemType Directory diff --git a/scripts/azure-pipelines/end-to-end-tests-dir/versions.ps1 b/scripts/azure-pipelines/end-to-end-tests-dir/versions.ps1 index f5f9de20b1..11550d7bd7 100644 --- a/scripts/azure-pipelines/end-to-end-tests-dir/versions.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-dir/versions.ps1 @@ -59,17 +59,40 @@ Throw-IfFailed Throw-IfFailed $CurrentTest = "default baseline" -$out = ./vcpkg $commonArgs "--feature-flags=versions" install --x-manifest-root=scripts/testing/version-files/default-baseline-1 +$out = ./vcpkg $commonArgs "--feature-flags=versions" install --x-manifest-root=scripts/testing/version-files/default-baseline-1 2>&1 | Out-String Throw-IfNotFailed -# if ($out -notmatch "Error: while checking out baseline" -or $out -notmatch " does not exist in ") -# { -# $out -# throw "Expected to fail due to missing baseline" -# } +if ($out -notmatch ".*Error: while checking out baseline.*") +{ + $out + throw "Expected to fail due to missing baseline" +} git fetch https://github.com/vicroms/test-registries -$CurrentTest = "default baseline" -./vcpkg $commonArgs --debug "--feature-flags=versions" install ` - "--x-manifest-root=scripts/testing/version-files/default-baseline-2" ` - "--x-builtin-port-versions-dir=scripts/testing/version-files/default-baseline-2/port_versions" -Throw-IfFailed +foreach ($opt_registries in @("",",registries")) +{ + Write-Trace "testing baselines: $opt_registries" + Refresh-TestRoot + $CurrentTest = "without default baseline 2 -- enabling versions should not change behavior" + Remove-Item -Recurse $buildtreesRoot/versioning -ErrorAction SilentlyContinue + ./vcpkg $commonArgs "--feature-flags=versions$opt_registries" install ` + "--dry-run" ` + "--x-manifest-root=scripts/testing/version-files/without-default-baseline-2" ` + "--x-builtin-port-versions-dir=scripts/testing/version-files/default-baseline-2/port_versions" + Throw-IfFailed + Require-FileNotExists $buildtreesRoot/versioning + + $CurrentTest = "default baseline 2" + ./vcpkg $commonArgs "--feature-flags=versions$opt_registries" install ` + "--dry-run" ` + "--x-manifest-root=scripts/testing/version-files/default-baseline-2" ` + "--x-builtin-port-versions-dir=scripts/testing/version-files/default-baseline-2/port_versions" + Throw-IfFailed + Require-FileExists $buildtreesRoot/versioning + + $CurrentTest = "using version features fails without flag" + ./vcpkg $commonArgs "--feature-flags=-versions$opt_registries" install ` + "--dry-run" ` + "--x-manifest-root=scripts/testing/version-files/default-baseline-2" ` + "--x-builtin-port-versions-dir=scripts/testing/version-files/default-baseline-2/port_versions" + Throw-IfNotFailed +} \ No newline at end of file diff --git a/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 b/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 index deadb210c1..29718bf764 100644 --- a/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 +++ b/scripts/azure-pipelines/end-to-end-tests-prelude.ps1 @@ -63,6 +63,10 @@ function Throw-IfNotFailed { } } +function Write-Trace ([string]$text) { + Write-Host (@($MyInvocation.ScriptName, ":", $MyInvocation.ScriptLineNumber, ": ", $text) -join "") +} + function Run-Vcpkg { Param( [Parameter(ValueFromRemainingArguments)] diff --git a/scripts/generatePortVersionsDb.py b/scripts/generatePortVersionsDb.py index 3b7de69421..3be4a939e4 100644 --- a/scripts/generatePortVersionsDb.py +++ b/scripts/generatePortVersionsDb.py @@ -35,7 +35,7 @@ def generate_port_versions_file(port_name): env = os.environ.copy() env['GIT_OPTIONAL_LOCKS'] = '0' output = subprocess.run( - [os.path.join(SCRIPT_DIRECTORY, '../vcpkg.exe'), + [os.path.join(SCRIPT_DIRECTORY, '../vcpkg'), 'x-history', port_name, '--x-json', f'--output={output_file_path}'], capture_output=True, encoding='utf-8', env=env) if output.returncode != 0: diff --git a/scripts/testing/version-files/default-baseline-1/vcpkg.json b/scripts/testing/version-files/default-baseline-1/vcpkg.json index f6d902393d..51e65c82ca 100644 --- a/scripts/testing/version-files/default-baseline-1/vcpkg.json +++ b/scripts/testing/version-files/default-baseline-1/vcpkg.json @@ -1,7 +1,7 @@ { "name": "default-baseline-test", "version-string": "0", - "$x-default-baseline": "fca18ba3572f8aebe3b8158c359db62a7e26134e", + "builtin-baseline": "fca18ba3572f8aebe3b8158c359db62a7e26134e", "dependencies": [ "zlib" ] diff --git a/scripts/testing/version-files/default-baseline-2/vcpkg.json b/scripts/testing/version-files/default-baseline-2/vcpkg.json index af353c09d7..f540ccba0b 100644 --- a/scripts/testing/version-files/default-baseline-2/vcpkg.json +++ b/scripts/testing/version-files/default-baseline-2/vcpkg.json @@ -1,7 +1,7 @@ { - "name": "default-baseline-test", + "name": "default-baseline-test-2", "version-string": "0", - "$x-default-baseline": "16002d9c2318dec4c69e02d9af8c0e11dca0d4c6", + "builtin-baseline": "16002d9c2318dec4c69e02d9af8c0e11dca0d4c6", "dependencies": [ "zlib" ] diff --git a/scripts/testing/version-files/without-default-baseline-2/port_versions/z-/zlib.json b/scripts/testing/version-files/without-default-baseline-2/port_versions/z-/zlib.json new file mode 100644 index 0000000000..f5ee7cb9dc --- /dev/null +++ b/scripts/testing/version-files/without-default-baseline-2/port_versions/z-/zlib.json @@ -0,0 +1,14 @@ +{ + "versions": [ + { + "git-tree": "7bb2b2f3783303a4dd41163553fe4cc103dc9262", + "version-string": "1.2.11", + "port-version": 9 + }, + { + "git-tree": "4927735fa9baca564ebddf6e6880de344b20d7a8", + "version-string": "1.2.11", + "port-version": 8 + } + ] +} \ No newline at end of file diff --git a/scripts/testing/version-files/without-default-baseline-2/vcpkg.json b/scripts/testing/version-files/without-default-baseline-2/vcpkg.json new file mode 100644 index 0000000000..839418fb71 --- /dev/null +++ b/scripts/testing/version-files/without-default-baseline-2/vcpkg.json @@ -0,0 +1,7 @@ +{ + "name": "without-default-baseline-test-2", + "version-string": "0", + "dependencies": [ + "zlib" + ] +} \ No newline at end of file diff --git a/scripts/vcpkg.schema.json b/scripts/vcpkg.schema.json index 9df3620869..c485aa4158 100644 --- a/scripts/vcpkg.schema.json +++ b/scripts/vcpkg.schema.json @@ -136,12 +136,9 @@ "$ref": "#/definitions/platform-expression" }, "version>=": { - "description": "Minimum required port version", - "$ref": "#/definitions/version-text" - }, - "port-version": { - "description": "Minimum port revision (defaults to 0)", - "$ref": "#/definitions/port-version" + "description": "Minimum required version", + "type": "string", + "pattern": "^[^#]+(#\\d+)?$" } }, "patternProperties": { @@ -256,6 +253,10 @@ "description": "An SPDX license expression at version 3.9.", "type": "string" }, + "builtin-baseline": { + "description": "A vcpkg repository commit for version control.", + "type": "string" + }, "dependencies": { "description": "Dependencies that are always required.", "type": "array", diff --git a/toolsrc/include/vcpkg/base/expected.h b/toolsrc/include/vcpkg/base/expected.h index 13e7b4bcbf..20c23f0773 100644 --- a/toolsrc/include/vcpkg/base/expected.h +++ b/toolsrc/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/toolsrc/include/vcpkg/base/files.h b/toolsrc/include/vcpkg/base/files.h index dd1b3a6c61..5f2e96e91e 100644 --- a/toolsrc/include/vcpkg/base/files.h +++ b/toolsrc/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; + ExclusiveFileLock(ExclusiveFileLock&&) = delete; + ExclusiveFileLock& operator=(ExclusiveFileLock&&) = delete; - clear(); - fs_ = other.fs_; - handle_ = std::exchange(other.handle_, fs::SystemHandle{}); - return *this; - } - - 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/toolsrc/include/vcpkg/base/json.h b/toolsrc/include/vcpkg/base/json.h index e7f3076f78..31696b757e 100644 --- a/toolsrc/include/vcpkg/base/json.h +++ b/toolsrc/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/toolsrc/include/vcpkg/base/jsonreader.h b/toolsrc/include/vcpkg/base/jsonreader.h index da086fa028..4aca02998a 100644 --- a/toolsrc/include/vcpkg/base/jsonreader.h +++ b/toolsrc/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/toolsrc/include/vcpkg/sourceparagraph.h b/toolsrc/include/vcpkg/sourceparagraph.h index f2dd0798b9..52ce539808 100644 --- a/toolsrc/include/vcpkg/sourceparagraph.h +++ b/toolsrc/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/toolsrc/include/vcpkg/vcpkgpaths.h b/toolsrc/include/vcpkg/vcpkgpaths.h index 36447b9f37..d36ebfb7cf 100644 --- a/toolsrc/include/vcpkg/vcpkgpaths.h +++ b/toolsrc/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/toolsrc/include/vcpkg/versions.h b/toolsrc/include/vcpkg/versions.h index 19b5546eaf..b26c90dfba 100644 --- a/toolsrc/include/vcpkg/versions.h +++ b/toolsrc/include/vcpkg/versions.h @@ -81,8 +81,7 @@ namespace vcpkg::Versions enum class Type { None, - Minimum, - Exact + Minimum }; }; } diff --git a/toolsrc/src/vcpkg-test/dependencies.cpp b/toolsrc/src/vcpkg-test/dependencies.cpp index 2831ea5a1a..8f936abc38 100644 --- a/toolsrc/src/vcpkg-test/dependencies.cpp +++ b/toolsrc/src/vcpkg-test/dependencies.cpp @@ -196,7 +196,7 @@ private: 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/toolsrc/src/vcpkg-test/manifests.cpp b/toolsrc/src/vcpkg-test/manifests.cpp index 39e4700750..0af9f4f968 100644 --- a/toolsrc/src/vcpkg-test/manifests.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/base/json.cpp b/toolsrc/src/vcpkg/base/json.cpp index 4a784235e0..d99fa73925 100644 --- a/toolsrc/src/vcpkg/base/json.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/commands.civerifyversions.cpp b/toolsrc/src/vcpkg/commands.civerifyversions.cpp index 84aab9c019..71ced9f76a 100644 --- a/toolsrc/src/vcpkg/commands.civerifyversions.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/help.cpp b/toolsrc/src/vcpkg/help.cpp index ded6592fbe..0556b4fa06 100644 --- a/toolsrc/src/vcpkg/help.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/install.cpp b/toolsrc/src/vcpkg/install.cpp index 80b4eae3ae..e86a78a764 100644 --- a/toolsrc/src/vcpkg/install.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/paragraphs.cpp b/toolsrc/src/vcpkg/paragraphs.cpp index 32103c76b9..2d28c49d70 100644 --- a/toolsrc/src/vcpkg/paragraphs.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/registries.cpp b/toolsrc/src/vcpkg/registries.cpp index b9afa089be..934e6537a0 100644 --- a/toolsrc/src/vcpkg/registries.cpp +++ b/toolsrc/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/toolsrc/src/vcpkg/sourceparagraph.cpp b/toolsrc/src/vcpkg/sourceparagraph.cpp index c4ae3e8556..61cc7f8264 100644 --- a/toolsrc/src/vcpkg/sourceparagraph.cpp +++ b/toolsrc/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,32 +484,33 @@ 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) - { - dep.constraint.type = Versions::Constraint::Type::Exact; - if (has_ge_constraint) - { - r.add_generic_error(type_name(), "cannot have both exact and minimum constraints simultaneously"); - } - } - else if (has_ge_constraint) + 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>=\")"); + auto h = dep.constraint.value.find('#'); + if (h != std::string::npos) + { + 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); + } } 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) + if (dep.constraint.type == Versions::Constraint::Type::Minimum) { - 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) - { - 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/toolsrc/src/vcpkg/vcpkgpaths.cpp b/toolsrc/src/vcpkg/vcpkgpaths.cpp index afd7247655..7926e7a5fb 100644 --- a/toolsrc/src/vcpkg/vcpkgpaths.cpp +++ b/toolsrc/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); - - if (!fs.exists(destination / "CONTROL") && !fs.exists(destination / "vcpkg.json")) + Files::Filesystem& fs = get_filesystem(); + fs::path destination = this->versions_output / fs::u8path(port_name) / fs::u8path(git_tree); + if (fs.exists(destination)) { - git_checkout_object( - *this, git_tree, local_repo, destination, this->versions_dot_git_dir, this->versions_work_tree); + return destination; } + + 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) + { + 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);