diff --git a/azure-pipelines/e2e-specs/autocomplete-posh-vcpkg.Tests.ps1 b/azure-pipelines/e2e-specs/autocomplete-posh-vcpkg.Tests.ps1 index 5d9aebe4e4..73eaaf9a23 100644 --- a/azure-pipelines/e2e-specs/autocomplete-posh-vcpkg.Tests.ps1 +++ b/azure-pipelines/e2e-specs/autocomplete-posh-vcpkg.Tests.ps1 @@ -30,7 +30,7 @@ BeforeAll { 'export', 'fetch', 'find', 'format-feature-baseline', 'format-manifest', 'hash', 'help', 'install', 'integrate', 'license-report', 'list', 'new', 'owns', 'portsdiff', 'remove', 'search', 'update', 'upgrade', 'use', 'version', 'x-add-version', 'x-check-support', 'x-init-registry', 'x-package-info', 'x-regenerate', 'x-set-installed', - 'x-test-features', 'x-update-baseline', 'x-update-registry', 'x-vsinstances' + 'x-test-features', 'x-update-baseline', 'x-update-registry', 'x-vsinstances', 'x-baseline-diff' ) CommonParameterList = @() CommandOptionList = @{ diff --git a/azure-pipelines/end-to-end-tests-dir/baseline-diff.ps1 b/azure-pipelines/end-to-end-tests-dir/baseline-diff.ps1 new file mode 100644 index 0000000000..f93bf11484 --- /dev/null +++ b/azure-pipelines/end-to-end-tests-dir/baseline-diff.ps1 @@ -0,0 +1,250 @@ +. $PSScriptRoot/../end-to-end-tests-prelude.ps1 + +$env:X_VCPKG_REGISTRIES_CACHE = Join-Path $TestingRoot 'registries' +New-Item -ItemType Directory -Force $env:X_VCPKG_REGISTRIES_CACHE | Out-Null + +# ===================================================================== +# Helper: write a minimal empty test port +# ===================================================================== +function New-TestPort { + param( + [string]$PortsRoot, + [string]$Name, + [string]$Version + ) + $portDir = Join-Path $PortsRoot $Name + New-Item -Path $portDir -ItemType Directory -Force | Out-Null + Set-Content -LiteralPath (Join-Path $portDir 'portfile.cmake') ` + -Value 'set(VCPKG_POLICY_EMPTY_PACKAGE enabled)' -Encoding Ascii + Set-Content -LiteralPath (Join-Path $portDir 'vcpkg.json') ` + -Value "{`"name`": `"$Name`", `"version`": `"$Version`"}" -Encoding Ascii +} + +# ===================================================================== +# Builtin registry tests use two real vcpkg release tags so that: +# - ref resolution is exercised against real tags in VCPKG_ROOT +# - the expected version change is stable and documented +# +# curl changed 8.11.0 -> 8.11.1 between the 2024.11.16 and 2024.12.16 +# releases (https://github.com/microsoft/vcpkg/releases). +# ===================================================================== +Write-Trace "Resolve vcpkg release tags to SHAs" +$oldTag = '2024.11.16' +$newTag = '2024.12.16' +# builtin-baseline in vcpkg.json must be a full 40-char SHA. +$oldSha = git -C $env:VCPKG_ROOT rev-parse $oldTag +Throw-IfFailed +$newSha = git -C $env:VCPKG_ROOT rev-parse $newTag +Throw-IfFailed + +# Feature flag needed because the manifest carries builtin-baseline. +$builtinArgs = @('--feature-flags=versions') + +# Manifest: depends on curl, whose version changed between the two releases. +$manifestDir = "$TestingRoot/manifest-builtin" +New-Item -Path $manifestDir -ItemType Directory | Out-Null +$manifestDir = (Get-Item $manifestDir).FullName +Set-Content -LiteralPath "$manifestDir/vcpkg.json" -Encoding Ascii -Value @" +{ + "name": "baseline-diff-test", + "version-string": "0", + "builtin-baseline": "$oldSha", + "dependencies": [ "curl" ] +} +"@ + +# ===================================================================== +# Test 1: Builtin registry — two explicit SHAs, expect known curl update +# ===================================================================== +Write-Trace "Test: builtin registry with two explicit SHAs" +$CurrentTest = 'x-baseline-diff builtin two SHAs' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs @builtinArgs ` + "--x-manifest-root=$manifestDir" $oldSha $newSha +Throw-IfFailed +Throw-IfNonContains -Actual $Output -Expected 'curl' +Throw-IfNonContains -Actual $Output -Expected '8.11.0' +Throw-IfNonContains -Actual $Output -Expected '8.11.1' + +# ===================================================================== +# Test 2: Builtin registry — one SHA, manifest builtin-baseline as old +# ===================================================================== +Write-Trace "Test: builtin registry with one SHA (manifest provides old baseline)" +$CurrentTest = 'x-baseline-diff builtin one SHA' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs @builtinArgs ` + "--x-manifest-root=$manifestDir" $newSha +Throw-IfFailed +Throw-IfNonContains -Actual $Output -Expected 'curl' +Throw-IfNonContains -Actual $Output -Expected '8.11.0' +Throw-IfNonContains -Actual $Output -Expected '8.11.1' + +# ===================================================================== +# Test 3: Builtin registry — real release tags as arguments (ref resolution) +# No temporary tags needed; the release tags already exist in VCPKG_ROOT. +# ===================================================================== +Write-Trace "Test: builtin registry with release tags (ref resolution)" +$CurrentTest = 'x-baseline-diff builtin with tags' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs @builtinArgs ` + "--x-manifest-root=$manifestDir" $oldTag $newTag +Throw-IfFailed +Throw-IfNonContains -Actual $Output -Expected 'curl' +Throw-IfNonContains -Actual $Output -Expected '8.11.0' +Throw-IfNonContains -Actual $Output -Expected '8.11.1' + +# ===================================================================== +# Test 4: Builtin registry — same tag twice, expect no changes +# ===================================================================== +Write-Trace "Test: builtin registry — same baseline, no changes" +$CurrentTest = 'x-baseline-diff builtin no change' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs @builtinArgs ` + "--x-manifest-root=$manifestDir" $newTag $newTag +Throw-IfFailed +Throw-IfContains -Actual $Output -Expected ' -> ' +Throw-IfNonContains -Actual $Output -Expected 'No changes to installed packages between baselines' + +# ===================================================================== +# Test 5: Missing manifest — must fail +# ===================================================================== +Write-Trace "Test: x-baseline-diff fails without a manifest" +$CurrentTest = 'x-baseline-diff no manifest' +$noManifestDir = "$TestingRoot/no-manifest-dir" +New-Item -Path $noManifestDir -ItemType Directory | Out-Null +Run-Vcpkg x-baseline-diff @commonArgs @builtinArgs "--x-manifest-root=$noManifestDir" $oldSha $newSha +Throw-IfNotFailed + +# ===================================================================== +# Test 6: One-arg mode fails when manifest has no builtin-baseline +# ===================================================================== +Write-Trace "Test: one-arg mode fails when manifest has no builtin-baseline" +$manifestDirNoBaseline = "$TestingRoot/manifest-no-baseline" +New-Item -Path $manifestDirNoBaseline -ItemType Directory | Out-Null +Set-Content -LiteralPath "$manifestDirNoBaseline/vcpkg.json" -Encoding Ascii -Value @" +{ + "name": "baseline-diff-test", + "version-string": "0", + "dependencies": [ "zlib" ] +} +"@ +$CurrentTest = 'x-baseline-diff one arg no manifest baseline' +Run-Vcpkg x-baseline-diff @commonArgs @builtinArgs ` + "--x-manifest-root=$manifestDirNoBaseline" $newSha +Throw-IfNotFailed + +# ===================================================================== +# Build a filesystem registry used as the DEFAULT registry. +# It carries two named baselines ("v1", "v2") with different versions +# of the same port — a supported but uncommon filesystem-registry +# feature (https://learn.microsoft.com/vcpkg/maintainers/registries +# #filesystem-registries). +# +# Port files live in versioned subdirectories so both versions are +# accessible simultaneously via the "path" entries. +# ===================================================================== +Write-Trace "Build filesystem registry with two named baselines" +$filesystemRegistryRoot = "$TestingRoot/filesystem-registry" +New-Item -Path $filesystemRegistryRoot -ItemType Directory | Out-Null +$filesystemRegistryRoot = (Get-Item $filesystemRegistryRoot).FullName + +New-TestPort -PortsRoot "$filesystemRegistryRoot/v1" -Name 'vcpkg-baseline-diff-test-a' -Version '1.0.0' +New-TestPort -PortsRoot "$filesystemRegistryRoot/v2" -Name 'vcpkg-baseline-diff-test-a' -Version '2.0.0' +New-Item -Path "$filesystemRegistryRoot/versions/v-" -ItemType Directory -Force | Out-Null +Set-Content -LiteralPath "$filesystemRegistryRoot/versions/baseline.json" -Encoding Ascii -Value @" +{ + "v1": { + "vcpkg-baseline-diff-test-a": { "baseline": "1.0.0", "port-version": 0 } + }, + "v2": { + "vcpkg-baseline-diff-test-a": { "baseline": "2.0.0", "port-version": 0 } + } +} +"@ +Set-Content -LiteralPath "$filesystemRegistryRoot/versions/v-/vcpkg-baseline-diff-test-a.json" -Encoding Ascii -Value @" +{ + "versions": [ + { "version": "2.0.0", "port-version": 0, "path": "$/v2/vcpkg-baseline-diff-test-a" }, + { "version": "1.0.0", "port-version": 0, "path": "$/v1/vcpkg-baseline-diff-test-a" } + ] +} +"@ + +# Manifest: plain dependency, no builtin-baseline needed. +# vcpkg-configuration.json sets the filesystem registry as default with +# "baseline": "v1" so that the 1-arg test can use it as the old baseline. +$manifestDirFs = "$TestingRoot/manifest-filesystem" +New-Item -Path $manifestDirFs -ItemType Directory | Out-Null +$manifestDirFs = (Get-Item $manifestDirFs).FullName + +Set-Content -LiteralPath "$manifestDirFs/vcpkg.json" -Encoding Ascii -Value @" +{ + "name": "baseline-diff-test", + "version-string": "0", + "dependencies": [ "vcpkg-baseline-diff-test-a" ] +} +"@ +Set-Content -LiteralPath "$manifestDirFs/vcpkg-configuration.json" -Encoding Ascii ` + -Value (ConvertTo-Json -Depth 5 @{ + "default-registry" = @{ + "kind" = "filesystem" + "path" = $filesystemRegistryRoot + "baseline" = "v1" + } + }) + +# ===================================================================== +# Test 7: Filesystem registry as default — two named baselines +# ===================================================================== +Write-Trace "Test: filesystem registry as default registry, two named baselines" +$CurrentTest = 'x-baseline-diff filesystem two baselines' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs '--feature-flags=registries' ` + "--x-manifest-root=$manifestDirFs" 'v1' 'v2' +Throw-IfFailed +Throw-IfNonContains -Actual $Output -Expected 'vcpkg-baseline-diff-test-a' +Throw-IfNonContains -Actual $Output -Expected '1.0.0' +Throw-IfNonContains -Actual $Output -Expected '2.0.0' + +# ===================================================================== +# Test 7b: Filesystem registry — one baseline arg, registry config +# baseline used as old +# ===================================================================== +Write-Trace "Test: filesystem registry, one arg (registry config baseline as old)" +$CurrentTest = 'x-baseline-diff filesystem one baseline' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs '--feature-flags=registries' ` + "--x-manifest-root=$manifestDirFs" 'v2' +Throw-IfFailed +Throw-IfNonContains -Actual $Output -Expected 'vcpkg-baseline-diff-test-a' +Throw-IfNonContains -Actual $Output -Expected '1.0.0' +Throw-IfNonContains -Actual $Output -Expected '2.0.0' + +# ===================================================================== +# Test 8: Git registry pointing at https://github.com/microsoft/vcpkg +# as the default registry — same SHAs as the builtin tests above since +# VCPKG_ROOT is a clone of that repo. Also exercises the +# is_builtin_git_registry ref-resolution code path. +# ===================================================================== +Write-Trace "Test: git registry (github.com/microsoft/vcpkg) as default registry" +$manifestDirGit = "$TestingRoot/manifest-git-registry" +New-Item -Path $manifestDirGit -ItemType Directory | Out-Null +$manifestDirGit = (Get-Item $manifestDirGit).FullName + +Set-Content -LiteralPath "$manifestDirGit/vcpkg.json" -Encoding Ascii -Value @" +{ + "name": "baseline-diff-test", + "version-string": "0", + "dependencies": [ "curl" ] +} +"@ +Set-Content -LiteralPath "$manifestDirGit/vcpkg-configuration.json" -Encoding Ascii ` + -Value (ConvertTo-Json -Depth 5 @{ + "default-registry" = @{ + "kind" = "git" + "repository" = "https://github.com/microsoft/vcpkg" + "baseline" = $oldSha + } + }) + +$CurrentTest = 'x-baseline-diff git registry two SHAs' +$Output = Run-VcpkgAndCaptureOutput x-baseline-diff @commonArgs '--feature-flags=registries' ` + "--x-manifest-root=$manifestDirGit" $oldSha $newSha +Throw-IfFailed +Throw-IfNonContains -Actual $Output -Expected 'curl' +Throw-IfNonContains -Actual $Output -Expected '8.11.0' +Throw-IfNonContains -Actual $Output -Expected '8.11.1' diff --git a/include/vcpkg/base/git.h b/include/vcpkg/base/git.h index dac57c61d7..b2bc1e343e 100644 --- a/include/vcpkg/base/git.h +++ b/include/vcpkg/base/git.h @@ -112,4 +112,11 @@ namespace vcpkg const Path& git_exe, const Path& builtin_ports_dir, StringView git_commit_id); + + // Resolves a git ref (tag, branch, or commit-ish) to a full 40-character SHA using `git rev-parse`. + // Returns nullopt and reports an error if the ref cannot be resolved. + Optional git_resolve_to_full_sha(DiagnosticContext& context, + const Path& git_exe, + GitRepoLocator locator, + StringView ref); } diff --git a/include/vcpkg/base/message-data.inc.h b/include/vcpkg/base/message-data.inc.h index 1ff76720e5..537c74749f 100644 --- a/include/vcpkg/base/message-data.inc.h +++ b/include/vcpkg/base/message-data.inc.h @@ -331,6 +331,10 @@ DECLARE_MESSAGE(BaselineConflict, "", "Specifying vcpkg-configuration.default-registry in a manifest file conflicts with built-in " "baseline.\nPlease remove one of these conflicting settings.") +DECLARE_MESSAGE(BaselineDiffNoChange, + (), + "Theoretically it is wrong to say installed, but this is probably the case most of the time", + "No changes to installed packages between baselines") DECLARE_MESSAGE(BaselineGitShowFailed, (msg::commit_sha), "", @@ -589,6 +593,21 @@ DECLARE_MESSAGE(CmdAddVersionOptOverwriteVersion, (), "", "Overwrites git-tree o DECLARE_MESSAGE(CmdAddVersionOptSkipFormatChk, (), "", "Skips the formatting check of vcpkg.json files") DECLARE_MESSAGE(CmdAddVersionOptSkipVersionFormatChk, (), "", "Skips the version format check") DECLARE_MESSAGE(CmdAddVersionOptVerbose, (), "", "Prints success messages rather than only errors") +DECLARE_MESSAGE(CmdBaselineDiffExample1, + (), + "This is a command line, only the and parts should be localized.", + "vcpkg x-baseline-diff ") +DECLARE_MESSAGE(CmdBaselineDiffMissingBuiltinBaseline, + (), + "", + "Only one commit was provided but the manifest does not have a 'builtin-baseline'. " + "Please specify two commits or add a 'builtin-baseline' to vcpkg.json.") +DECLARE_MESSAGE(CmdBaselineDiffMissingRegistryBaseline, + (), + "", + "Only one commit was provided but the default registry in vcpkg-configuration.json does not " + "have a 'baseline'. Please specify two commits or add a 'baseline' to the default registry.") +DECLARE_MESSAGE(CmdBaselineDiffSynopsis, (), "", "Computes version changes to installable packages between baselines") DECLARE_MESSAGE(CmdBootstrapStandaloneSynopsis, (), "", "Bootstraps a vcpkg root from only a vcpkg binary") DECLARE_MESSAGE(CmdBuildExternalExample1, (), @@ -1056,6 +1075,7 @@ DECLARE_MESSAGE(DependencyWillFail, "'cascade' is a keyword and should not be translated", "Dependency {feature_spec} will not build => cascade") DECLARE_MESSAGE(DetectCompilerHash, (msg::triplet), "", "Detecting compiler hash for triplet {triplet}...") +DECLARE_MESSAGE(DirectDependencies, (), "", "Direct dependencies") DECLARE_MESSAGE(DirectoriesRelativeToThePackageDirectoryHere, (), "", @@ -1972,7 +1992,10 @@ DECLARE_MESSAGE(InvalidCommentStyle, "", "vcpkg does not support c-style comments, however most objects allow $-prefixed fields to be used as " "comments.") -DECLARE_MESSAGE(InvalidCommitId, (msg::commit_sha), "", "Invalid commit id: {commit_sha}") +DECLARE_MESSAGE(InvalidCommitId, + (msg::commit_sha), + "", + "Invalid commit id (expected lowercase 40 hexadecimal characters): {commit_sha}") DECLARE_MESSAGE(InvalidDefaultFeatureName, (), "", "'default' is a reserved feature name") DECLARE_MESSAGE(InvalidFeature, (), @@ -1988,6 +2011,10 @@ DECLARE_MESSAGE(InvalidFormatString, (msg::actual), "{actual} is the provided format string", "invalid format string: {actual}") +DECLARE_MESSAGE(InvalidGitRef, + (msg::value), + "{value} is a git branch, tag or short git sha", + "Could not resolve git ref '{value}'") DECLARE_MESSAGE(InvalidHexDigit, (), "", "Invalid hex digit in unicode escape") DECLARE_MESSAGE(InvalidIntegerConst, (msg::count), "", "Invalid integer constant: {count}") DECLARE_MESSAGE(InvalidLibraryMissingLinkerMembers, (), "", "Library was invalid: could not find a linker member.") @@ -2173,6 +2200,7 @@ DECLARE_MESSAGE(MissingDependency, (msg::spec, msg::package_name), "", "Package {spec} is installed, but dependency {package_name} is not.") +DECLARE_MESSAGE(MissingManifestFile, (), "", "Couldn't find a manifest file (vcpkg.json file)") DECLARE_MESSAGE(MissingOption, (msg::option), "", "This command requires --{option}") DECLARE_MESSAGE(MissingOrInvalidIdentifer, (), "", "missing or invalid identifier") DECLARE_MESSAGE(MissingPortSuggestPullRequest, @@ -2867,6 +2895,7 @@ DECLARE_MESSAGE(ToUpdatePackages, "To update these packages and all dependencies, run\n{command_name} upgrade'") DECLARE_MESSAGE(TrailingCommaInArray, (), "", "Trailing comma in array") DECLARE_MESSAGE(TrailingCommaInObj, (), "", "Trailing comma in an object") +DECLARE_MESSAGE(TransitiveDependencies, (), "", "Transitive dependencies") DECLARE_MESSAGE(TripletLabel, (), "", "Triplet:") DECLARE_MESSAGE(TripletFileNotFound, (msg::triplet), "", "Triplet file {triplet}.cmake not found") DECLARE_MESSAGE(TwoFeatureFlagsSpecified, @@ -3119,6 +3148,10 @@ DECLARE_MESSAGE(UpdateBaselineNoUpdate, "example of {value} is '5507daa796359fe8d45418e694328e878ac2b82f'", "registry '{url}' not updated: '{value}'") DECLARE_MESSAGE(UpdateBaselineRemoteGitError, (msg::url), "", "git failed to fetch remote repository '{url}'") +DECLARE_MESSAGE(UpdateBaselineSuggestBaselineDiff, + (msg::old_value, msg::new_value), + "example of {old_value}, {new_value} is '5507daa796359fe8d45418e694328e878ac2b82f'", + "To see what packages changed run: vcpkg x-baseline-diff {old_value} {new_value}") DECLARE_MESSAGE(UpdateBaselineUpdatedBaseline, (msg::url, msg::old_value, msg::new_value), "example of {old_value}, {new_value} is '5507daa796359fe8d45418e694328e878ac2b82f'", diff --git a/include/vcpkg/commands.baseline-diff.h b/include/vcpkg/commands.baseline-diff.h new file mode 100644 index 0000000000..4692be2de9 --- /dev/null +++ b/include/vcpkg/commands.baseline-diff.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +namespace vcpkg +{ + extern const CommandMetadata CommandBaselineDiffMetadata; + void command_baseline_diff_and_exit(const VcpkgCmdArguments& args, + const VcpkgPaths& paths, + vcpkg::Triplet default_triplet, + vcpkg::Triplet host_triplet); +} diff --git a/include/vcpkg/commands.install.h b/include/vcpkg/commands.install.h index eda02ad658..3535ea52c5 100644 --- a/include/vcpkg/commands.install.h +++ b/include/vcpkg/commands.install.h @@ -86,7 +86,7 @@ namespace vcpkg std::unique_ptr parse_manifest_scf_or_exit(const ManifestAndPath& manifest, const VcpkgPaths& paths, - bool is_default_builtin_registry); + bool is_default_builtin_files_registry); std::vector get_manifest_features(const ParsedArguments& options, const SourceParagraph& manifest_core, diff --git a/include/vcpkg/registries.h b/include/vcpkg/registries.h index 9fa1fd45bd..3bfc8eb6a6 100644 --- a/include/vcpkg/registries.h +++ b/include/vcpkg/registries.h @@ -130,7 +130,7 @@ namespace vcpkg const RegistryImplementation* default_registry() const { return default_registry_.get(); } - bool is_default_builtin_registry() const; + bool is_default_builtin_files_registry() const; // returns whether the registry set has any modifications to the default // (i.e., whether `default_registry` was set, or `registries` had any entries) diff --git a/include/vcpkg/sourceparagraph.h b/include/vcpkg/sourceparagraph.h index 6b21640dbf..14ecbeb915 100644 --- a/include/vcpkg/sourceparagraph.h +++ b/include/vcpkg/sourceparagraph.h @@ -265,7 +265,7 @@ namespace vcpkg ExpectedL check_against_feature_flags(const Path& origin, const FeatureFlagSettings& flags, - bool is_default_builtin_registry = true) const; + bool is_default_builtin_files_registry = true) const; const std::string& to_name() const noexcept { return core_paragraph->name; } VersionScheme to_version_scheme() const noexcept { return core_paragraph->version_scheme; } diff --git a/locales/messages.json b/locales/messages.json index b2db8c8c8f..dcf2256f58 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -210,6 +210,8 @@ "AzcopyFailedToPutBlob": "azcopy failed to upload a file to {url} with exit code {exit_code} and http code {value}.", "_AzcopyFailedToPutBlob.comment": "azcopy is the name of a program. {value} is an HTTP status code. An example of {exit_code} is 127. An example of {url} is https://github.com/microsoft/vcpkg.", "BaselineConflict": "Specifying vcpkg-configuration.default-registry in a manifest file conflicts with built-in baseline.\nPlease remove one of these conflicting settings.", + "BaselineDiffNoChange": "No changes to installed packages between baselines", + "_BaselineDiffNoChange.comment": "Theoretically it is wrong to say installed, but this is probably the case most of the time", "BaselineGitShowFailed": "while checking out baseline from commit '{commit_sha}', failed to `git show` versions/baseline.json. This may be fixed by fetching commits with `git fetch`.", "_BaselineGitShowFailed.comment": "An example of {commit_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949.", "BaselineMissing": "{package_name} is not assigned a version", @@ -342,6 +344,11 @@ "CmdAddVersionOptSkipVersionFormatChk": "Skips the version format check", "CmdAddVersionOptVerbose": "Prints success messages rather than only errors", "CmdAddVersionSynopsis": "Adds a version to the version database", + "CmdBaselineDiffExample1": "vcpkg x-baseline-diff ", + "_CmdBaselineDiffExample1.comment": "This is a command line, only the and parts should be localized.", + "CmdBaselineDiffMissingBuiltinBaseline": "Only one commit was provided but the manifest does not have a 'builtin-baseline'. Please specify two commits or add a 'builtin-baseline' to vcpkg.json.", + "CmdBaselineDiffMissingRegistryBaseline": "Only one commit was provided but the default registry in vcpkg-configuration.json does not have a 'baseline'. Please specify two commits or add a 'baseline' to the default registry.", + "CmdBaselineDiffSynopsis": "Computes version changes to installable packages between baselines", "CmdBootstrapStandaloneSynopsis": "Bootstraps a vcpkg root from only a vcpkg binary", "CmdBuildExample1": "vcpkg build ", "_CmdBuildExample1.comment": "This is a command line, only the <>s part should be localized", @@ -609,6 +616,7 @@ "_DependencyWillFail.comment": "'cascade' is a keyword and should not be translated An example of {feature_spec} is zlib[featurea,featureb].", "DetectCompilerHash": "Detecting compiler hash for triplet {triplet}...", "_DetectCompilerHash.comment": "An example of {triplet} is x64-windows.", + "DirectDependencies": "Direct dependencies", "DirectoriesRelativeToThePackageDirectoryHere": "the directories are relative to ${{CURRENT_PACKAGES_DIR}} here", "DllsRelativeToThePackageDirectoryHere": "the DLLs are relative to ${{CURRENT_PACKAGES_DIR}} here", "DocumentedFieldsSuggestUpdate": "If these are documented fields that should be recognized try updating the vcpkg tool.", @@ -1069,7 +1077,7 @@ "InvalidCodeUnit": "invalid code unit", "InvalidCommandArgSort": "Value of --sort must be one of 'lexicographical', 'topological', 'reverse'.", "InvalidCommentStyle": "vcpkg does not support c-style comments, however most objects allow $-prefixed fields to be used as comments.", - "InvalidCommitId": "Invalid commit id: {commit_sha}", + "InvalidCommitId": "Invalid commit id (expected lowercase 40 hexadecimal characters): {commit_sha}", "_InvalidCommitId.comment": "An example of {commit_sha} is 7cfad47ae9f68b183983090afd6337cd60fd4949.", "InvalidDefaultFeatureName": "'default' is a reserved feature name", "InvalidFeature": "features must be lowercase alphanumeric+hyphens, and not one of the reserved names", @@ -1081,6 +1089,8 @@ "_InvalidFloatingPointConst.comment": "An example of {count} is 42.", "InvalidFormatString": "invalid format string: {actual}", "_InvalidFormatString.comment": "{actual} is the provided format string", + "InvalidGitRef": "Could not resolve git ref '{value}'", + "_InvalidGitRef.comment": "{value} is a git branch, tag or short git sha", "InvalidHexDigit": "Invalid hex digit in unicode escape", "InvalidIntegerConst": "Invalid integer constant: {count}", "_InvalidIntegerConst.comment": "An example of {count} is 42.", @@ -1179,6 +1189,7 @@ "MissingClosingParen": "missing closing )", "MissingDependency": "Package {spec} is installed, but dependency {package_name} is not.", "_MissingDependency.comment": "An example of {spec} is zlib:x64-windows. An example of {package_name} is zlib.", + "MissingManifestFile": "Couldn't find a manifest file (vcpkg.json file)", "MissingOption": "This command requires --{option}", "_MissingOption.comment": "An example of {option} is editable.", "MissingOrInvalidIdentifer": "missing or invalid identifier", @@ -1491,6 +1502,7 @@ "_TotalInstallTimeSuccess.comment": "An example of {elapsed} is 3.532 min.", "TrailingCommaInArray": "Trailing comma in array", "TrailingCommaInObj": "Trailing comma in an object", + "TransitiveDependencies": "Transitive dependencies", "TripletFileNotFound": "Triplet file {triplet}.cmake not found", "_TripletFileNotFound.comment": "An example of {triplet} is x64-windows.", "TripletLabel": "Triplet:", @@ -1620,6 +1632,8 @@ "_UpdateBaselineNoUpdate.comment": "example of {value} is '5507daa796359fe8d45418e694328e878ac2b82f' An example of {url} is https://github.com/microsoft/vcpkg.", "UpdateBaselineRemoteGitError": "git failed to fetch remote repository '{url}'", "_UpdateBaselineRemoteGitError.comment": "An example of {url} is https://github.com/microsoft/vcpkg.", + "UpdateBaselineSuggestBaselineDiff": "To see what packages changed run: vcpkg x-baseline-diff {old_value} {new_value}", + "_UpdateBaselineSuggestBaselineDiff.comment": "example of {old_value}, {new_value} is '5507daa796359fe8d45418e694328e878ac2b82f'", "UpdateBaselineUpdatedBaseline": "updated registry '{url}': baseline '{old_value}' -> '{new_value}'", "_UpdateBaselineUpdatedBaseline.comment": "example of {old_value}, {new_value} is '5507daa796359fe8d45418e694328e878ac2b82f' An example of {url} is https://github.com/microsoft/vcpkg.", "UpgradeInManifest": "Upgrade upgrades a classic mode installation and thus does not support manifest mode. Consider updating your dependencies by updating your baseline to a current value with vcpkg x-update-baseline and running vcpkg install.", diff --git a/src/vcpkg/base/git.cpp b/src/vcpkg/base/git.cpp index b5224afd47..b06564bdcb 100644 --- a/src/vcpkg/base/git.cpp +++ b/src/vcpkg/base/git.cpp @@ -545,6 +545,23 @@ namespace vcpkg return nullopt; } + Optional git_resolve_to_full_sha(DiagnosticContext& context, + const Path& git_exe, + GitRepoLocator locator, + StringView ref) + { + const auto commitish = fmt::format("{}^{{commit}}", ref); + StringView args[] = {StringLiteral{"rev-parse"}, commitish}; + auto cmd = make_git_command(git_exe, locator, args); + auto maybe_output = cmd_execute_and_capture_output(context, cmd); + if (auto output = check_zero_exit_code(context, cmd, maybe_output)) + { + Strings::inplace_trim_end(*output); + return std::move(*output); + } + return nullopt; + } + bool check_commit_exists(DiagnosticContext& context, const Path& git_exe, const Path& builtin_ports_dir, diff --git a/src/vcpkg/commands.baseline-diff.cpp b/src/vcpkg/commands.baseline-diff.cpp new file mode 100644 index 0000000000..4b10cd9253 --- /dev/null +++ b/src/vcpkg/commands.baseline-diff.cpp @@ -0,0 +1,309 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +using namespace vcpkg; + +namespace +{ + constexpr CommandSwitch switches[] = { + {SwitchXNoDefaultFeatures, msgHelpTxtOptManifestNoDefault}, + }; + + static constexpr CommandMultiSetting multisettings[] = { + {SwitchXFeature, msgHelpTxtOptManifestFeature}, + }; + + bool print_lines(const msg::MessageT<>& header, std::vector&& lines) + { + if (lines.empty()) + { + return false; + } + + Util::sort_unique_erase(lines); + msg::print(msg::format(header).append_raw(":\n")); + for (const auto& line : lines) + { + msg::write_unlocalized_text(Color::none, line); + msg::write_unlocalized_text(Color::none, "\n"); + } + msg::write_unlocalized_text(Color::none, "\n"); + return true; + } + +} // unnamed namespace + +namespace vcpkg +{ + constexpr CommandMetadata CommandBaselineDiffMetadata{ + "x-baseline-diff", + msgCmdBaselineDiffSynopsis, + {msgCmdBaselineDiffExample1, "vcpkg x-baseline-diff 2026.02.27 2026.03.18"}, + "https://learn.microsoft.com/vcpkg/commands/baseline-diff", + AutocompletePriority::Public, + 1, + 2, + {switches, {}, multisettings}, + nullptr, + }; + + void command_baseline_diff_and_exit(const VcpkgCmdArguments& args, + const VcpkgPaths& paths, + Triplet default_triplet, + Triplet host_triplet) + { + const auto* manifest = paths.get_manifest(); + if (manifest == nullptr) + { + Checks::msg_exit_with_message(VCPKG_LINE_INFO, msgMissingManifestFile); + } + auto options = args.parse_arguments(CommandBaselineDiffMetadata); + + auto& fs = paths.get_filesystem(); + InstallAndBuildDatabaseLock installed_lock{ + fs, paths.installed(), paths.buildtrees(), paths.packages(), args.wait_for_lock, args.ignore_lock_failures}; + auto var_provider_storage = CMakeVars::make_triplet_cmake_var_provider(paths, installed_lock); + auto& var_provider = *var_provider_storage; + + auto configuration = paths.get_configuration(); + auto registry_set = configuration.instantiate_registry_set(paths); + // is_default_builtin_files_registry() only matches BuiltinFilesRegistry (kind JsonIdBuiltinFiles), + // but when the manifest carries a builtin-baseline, instantiate_registry_set synthesises a + // RegistryConfig with kind "builtin" that creates BuiltinGitRegistry (kind JsonIdBuiltinGit) instead. + // Both are the local vcpkg clone, so treat either builtin kind as "is default builtin". + bool is_default_builtin = + registry_set->default_registry() && (registry_set->default_registry()->kind() == JsonIdBuiltinFiles || + registry_set->default_registry()->kind() == JsonIdBuiltinGit); + auto manifest_scf = + parse_manifest_scf_or_exit(*manifest, paths, registry_set->is_default_builtin_files_registry()); + + // Determine the two baseline refs. If only one arg, use the manifest's builtin-baseline as the old one. + StringView old_baseline_ref; + StringView new_baseline_ref; + if (options.command_arguments.size() == 2) + { + old_baseline_ref = options.command_arguments[0]; + new_baseline_ref = options.command_arguments[1]; + } + else + { + // Prefer builtin-baseline from the manifest; fall back to the baseline field of + // an explicitly configured default registry (e.g. a git registry in + // vcpkg-configuration.json). + const std::string* old_ref = manifest_scf->core_paragraph->builtin_baseline.get(); + if (!old_ref) + { + auto* default_reg = configuration.config.default_reg.get(); + if (default_reg) + { + old_ref = default_reg->baseline.get(); + if (!old_ref) + { + Checks::msg_exit_with_message(VCPKG_LINE_INFO, msgCmdBaselineDiffMissingRegistryBaseline); + } + } + else + { + Checks::msg_exit_with_message(VCPKG_LINE_INFO, msgCmdBaselineDiffMissingBuiltinBaseline); + } + } + old_baseline_ref = *old_ref; + new_baseline_ref = options.command_arguments[0]; + } + + // Resolve tags and branch names to full SHAs when a local vcpkg clone is available. + // This covers two cases: + // 1. The builtin registry (no explicit default-registry configured, cloned mode). + // 2. An explicit git registry pointing at https://github.com/microsoft/vcpkg while + // running from a local clone — the clone already has the same commits/tags. + // In both cases we use the git dir belonging to the versions directory so that + // --x-builtin-registry-versions-dir works correctly in tests. + std::string old_baseline; + std::string new_baseline; + + // Determine whether the configured default registry is the vcpkg GitHub repo. + bool is_builtin_git_registry = false; + if (!is_default_builtin) + { + if (auto* default_reg = configuration.config.default_reg.get()) + { + auto* kind = default_reg->kind.get(); + auto* repo = default_reg->repo.get(); + is_builtin_git_registry = kind && *kind == JsonIdGit && repo && *repo == builtin_registry_git_url; + } + } + + // Resolve tags and branch names via the local vcpkg clone when available. + // - Builtin registry (cloned mode): resolution is required; use paths.root. + // - Git registry pointing at the vcpkg GitHub repo while in cloned mode: try + // paths.root opportunistically; fall back to raw strings if unavailable. + // - Any other registry: no resolution, arguments must already be full SHAs. + // + // We use CurrentDirectory with paths.root (the vcpkg root) rather than + // DotGitDir so that git can locate packed-refs and tag objects reliably. + // Use the git root for ref resolution when a local vcpkg clone is available. + // versions_dot_git_dir() walks up from the builtin registry versions dir to find the + // repo root, so --x-builtin-registry-versions-dir is respected in tests. + const bool need_resolve = is_default_builtin || is_builtin_git_registry; + Path local_git_root; // non-empty when a usable local clone was found + if (need_resolve) + { + auto git_dir_result = paths.versions_dot_git_dir(); + if (auto* p = git_dir_result.get(); p && !p->empty()) + { + // parent_path() of the .git dir is the repo working directory + local_git_root = p->parent_path(); + } + } + + if (!local_git_root.empty()) + { + const auto* git_exe = paths.get_tool_path(console_diagnostic_context, Tools::GIT); + if (!git_exe) Checks::exit_fail(VCPKG_LINE_INFO); + + GitRepoLocator locator{GitRepoLocatorKind::CurrentDirectory, local_git_root}; + const auto resolve_baseline_ref = [&](StringView baseline) { + if (is_git_sha(baseline)) + { + return baseline.to_string(); + } + auto maybe_resolved = git_resolve_to_full_sha(console_diagnostic_context, *git_exe, locator, baseline); + Checks::msg_check_exit( + VCPKG_LINE_INFO, maybe_resolved.has_value(), msgInvalidGitRef, msg::value = baseline); + return *maybe_resolved.get(); + }; + old_baseline = resolve_baseline_ref(old_baseline_ref); + new_baseline = resolve_baseline_ref(new_baseline_ref); + } + else // no local git clone available or not a resolvable registry + { + old_baseline = old_baseline_ref.to_string(); + new_baseline = new_baseline_ref.to_string(); + + // Filesystem registries use named string keys as baselines, not commit SHAs. + // For builtin and git registries the argument must be a full 40-character SHA. + bool needs_sha_validation = true; + if (auto* default_reg = configuration.config.default_reg.get()) + { + auto* kind = default_reg->kind.get(); + needs_sha_validation = !kind || *kind != JsonIdFilesystem; + } + if (needs_sha_validation) + { + Checks::msg_check_exit( + VCPKG_LINE_INFO, is_git_sha(old_baseline), msgInvalidCommitId, msg::commit_sha = old_baseline); + Checks::msg_check_exit( + VCPKG_LINE_INFO, is_git_sha(new_baseline), msgInvalidCommitId, msg::commit_sha = new_baseline); + } + } + + const std::string* baselines[2] = {&old_baseline, &new_baseline}; + + const auto& manifest_core = *manifest_scf->core_paragraph; + PackageSpec toplevel{manifest_core.name, default_triplet}; + auto features = get_manifest_features(options, manifest_core, var_provider, toplevel, host_triplet); + + auto dependencies = get_manifest_dependencies(*manifest_scf, features); + + ActionPlan plan[2]; + for (size_t i = 0; i < 2; ++i) + { + if (auto default_reg = configuration.config.default_reg.get()) + { + default_reg->baseline = *baselines[i]; + } + else + { + RegistryConfig synthesized_registry; + synthesized_registry.kind = JsonIdBuiltin.to_string(); + synthesized_registry.baseline = *baselines[i]; + configuration.config.default_reg.emplace(synthesized_registry); + } + + auto updated_registry_set = configuration.instantiate_registry_set(paths); + auto verprovider = make_versioned_portfile_provider(*updated_registry_set); + auto baseprovider = make_baseline_provider(*updated_registry_set); + + auto extended_overlay_port_directories = paths.overlay_ports; + + auto oprovider = make_manifest_provider(fs, + extended_overlay_port_directories, + manifest->path, + std::make_unique(manifest_scf->clone())); + PackagesDirAssigner packages_dir_assigner{paths.packages()}; + const CreateInstallPlanOptions create_options{ + nullptr, host_triplet, UnsupportedPortAction::Warn, UseHeadVersion::No, Editable::No}; + plan[i] = create_versioned_install_plan(*verprovider, + *baseprovider, + *oprovider, + var_provider, + dependencies, + manifest_core.overrides, + toplevel, + packages_dir_assigner, + create_options) + .value_or_exit(VCPKG_LINE_INFO); + } + + std::map versions; + for (const auto& action : plan[0].install_actions) + { + versions[action.spec] = action.version.to_string(); + } + std::vector user_requested; + std::vector transitive; + for (const auto& action : plan[1].install_actions) + { + auto oldIter = versions.find(action.spec); + auto newVersion = action.version.to_string(); + auto& vec = action.request_type == RequestType::USER_REQUESTED ? user_requested : transitive; + if (oldIter == versions.end()) + { + vec.push_back(fmt::format("{}: new: {}", action.spec.name(), newVersion)); + } + else if (oldIter->second != newVersion) + { + vec.push_back(fmt::format("{}: {} -> {}", action.spec.name(), oldIter->second, newVersion)); + } + } + bool any_changes = false; + any_changes |= print_lines(msgDirectDependencies, std::move(user_requested)); + any_changes |= print_lines(msgTransitiveDependencies, std::move(transitive)); + if (!any_changes) + { + msg::println(msgBaselineDiffNoChange); + } + + Checks::exit_success(VCPKG_LINE_INFO); + } +} // namespace vcpkg diff --git a/src/vcpkg/commands.cpp b/src/vcpkg/commands.cpp index 325b3830a5..d4a773824c 100644 --- a/src/vcpkg/commands.cpp +++ b/src/vcpkg/commands.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -127,6 +128,7 @@ namespace vcpkg {CommandInstallMetadata, command_install_and_exit}, {CommandRemoveMetadata, command_remove_and_exit}, {CommandTestFeaturesMetadata, command_test_features_and_exit}, + {CommandBaselineDiffMetadata, command_baseline_diff_and_exit}, {CommandSetInstalledMetadata, command_set_installed_and_exit}, {CommandUpgradeMetadata, command_upgrade_and_exit}, {CommandZPrintConfigMetadata, command_z_print_config_and_exit}, diff --git a/src/vcpkg/commands.depend-info.cpp b/src/vcpkg/commands.depend-info.cpp index 3d35ffbba1..ba07fcf819 100644 --- a/src/vcpkg/commands.depend-info.cpp +++ b/src/vcpkg/commands.depend-info.cpp @@ -486,7 +486,7 @@ namespace vcpkg nullptr, host_triplet, unsupported_port_action, UseHeadVersion::No, Editable::No}; auto manifest_scf = - parse_manifest_scf_or_exit(*manifest, paths, registry_set->is_default_builtin_registry()); + parse_manifest_scf_or_exit(*manifest, paths, registry_set->is_default_builtin_files_registry()); const auto& manifest_core = *manifest_scf->core_paragraph; PackageSpec toplevel{manifest_core.name, default_triplet}; auto features = get_manifest_features(options, manifest_core, var_provider, toplevel, host_triplet); @@ -494,7 +494,7 @@ namespace vcpkg auto dependencies = get_manifest_dependencies(*manifest_scf, features); const bool add_builtin_ports_directory_as_overlay = - registry_set->is_default_builtin_registry() && !paths.use_git_default_registry(); + registry_set->is_default_builtin_files_registry() && !paths.use_git_default_registry(); auto verprovider = make_versioned_portfile_provider(*registry_set); auto baseprovider = make_baseline_provider(*registry_set); diff --git a/src/vcpkg/commands.install.cpp b/src/vcpkg/commands.install.cpp index e01cd7e4bd..8d904a9e06 100644 --- a/src/vcpkg/commands.install.cpp +++ b/src/vcpkg/commands.install.cpp @@ -92,7 +92,7 @@ namespace vcpkg { std::unique_ptr parse_manifest_scf_or_exit(const ManifestAndPath& manifest, const VcpkgPaths& paths, - bool is_default_builtin_registry) + bool is_default_builtin_files_registry) { auto maybe_manifest_scf = SourceControlFile::parse_project_manifest_object(manifest.path, manifest.manifest, out_sink); @@ -109,7 +109,8 @@ namespace vcpkg } auto manifest_scf = std::move(maybe_manifest_scf).value(VCPKG_LINE_INFO); - manifest_scf->check_against_feature_flags(manifest.path, paths.get_feature_flags(), is_default_builtin_registry) + manifest_scf + ->check_against_feature_flags(manifest.path, paths.get_feature_flags(), is_default_builtin_files_registry) .value_or_exit(VCPKG_LINE_INFO); return manifest_scf; } @@ -1407,7 +1408,7 @@ namespace vcpkg if (manifest) { auto manifest_scf = - parse_manifest_scf_or_exit(*manifest, paths, registry_set->is_default_builtin_registry()); + parse_manifest_scf_or_exit(*manifest, paths, registry_set->is_default_builtin_files_registry()); const auto& manifest_core = *manifest_scf->core_paragraph; PackageSpec toplevel{manifest_core.name, default_triplet}; auto features = get_manifest_features(options, manifest_core, var_provider, toplevel, host_triplet); @@ -1415,7 +1416,7 @@ namespace vcpkg auto dependencies = get_manifest_dependencies(*manifest_scf, features); const bool add_builtin_ports_directory_as_overlay = - registry_set->is_default_builtin_registry() && !paths.use_git_default_registry(); + registry_set->is_default_builtin_files_registry() && !paths.use_git_default_registry(); auto verprovider = make_versioned_portfile_provider(*registry_set); auto baseprovider = make_baseline_provider(*registry_set); diff --git a/src/vcpkg/commands.update-baseline.cpp b/src/vcpkg/commands.update-baseline.cpp index 8170237a86..c8b199c4ab 100644 --- a/src/vcpkg/commands.update-baseline.cpp +++ b/src/vcpkg/commands.update-baseline.cpp @@ -97,6 +97,11 @@ namespace vcpkg Checks::exit_success(VCPKG_LINE_INFO); } + // Track the old and new baselines of the primary registry so we can suggest + // x-baseline-diff afterwards. + std::string primary_old_baseline; + std::string primary_new_baseline; + if (has_builtin_baseline || add_builtin_baseline) { // remove default_reg, since that's filled in with the builtin-baseline @@ -109,7 +114,9 @@ namespace vcpkg synthesized_registry.baseline = p->string(VCPKG_LINE_INFO).to_string(); } + primary_old_baseline = synthesized_registry.baseline.value_or(""); update_baseline_in_config(paths, synthesized_registry); + primary_new_baseline = synthesized_registry.baseline.value_or(""); if (auto p = synthesized_registry.baseline.get()) { @@ -119,7 +126,9 @@ namespace vcpkg if (auto default_reg = configuration.config.default_reg.get()) { + primary_old_baseline = default_reg->baseline.value_or(""); update_baseline_in_config(paths, *default_reg); + primary_new_baseline = default_reg->baseline.value_or(""); } for (auto& reg : configuration.config.registries) @@ -154,6 +163,13 @@ namespace vcpkg paths.get_filesystem().write_contents(manifest.path, Json::stringify(manifest.manifest), VCPKG_LINE_INFO); } + if (!dry_run && !primary_old_baseline.empty() && primary_new_baseline != primary_old_baseline) + { + msg::println(msgUpdateBaselineSuggestBaselineDiff, + msg::old_value = primary_old_baseline, + msg::new_value = primary_new_baseline); + } + Checks::exit_success(VCPKG_LINE_INFO); } } // namespace vcpkg diff --git a/src/vcpkg/registries.cpp b/src/vcpkg/registries.cpp index 659f308b38..e7525e102a 100644 --- a/src/vcpkg/registries.cpp +++ b/src/vcpkg/registries.cpp @@ -1199,11 +1199,11 @@ namespace vcpkg return impl->get_baseline_version(port_name); } - bool RegistrySet::is_default_builtin_registry() const + bool RegistrySet::is_default_builtin_files_registry() const { return default_registry_ && default_registry_->kind() == JsonIdBuiltinFiles; } - bool RegistrySet::has_modifications() const { return !registries_.empty() || !is_default_builtin_registry(); } + bool RegistrySet::has_modifications() const { return !registries_.empty() || !is_default_builtin_files_registry(); } } // namespace vcpkg namespace diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index 0cf7e5ad3a..587edd5377 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -1660,7 +1660,7 @@ namespace vcpkg ExpectedL SourceControlFile::check_against_feature_flags(const Path& origin, const FeatureFlagSettings& flags, - bool is_default_builtin_registry) const + bool is_default_builtin_files_registry) const { if (!flags.versions) { @@ -1711,7 +1711,7 @@ namespace vcpkg } else { - if (!core_paragraph->builtin_baseline.has_value() && is_default_builtin_registry) + if (!core_paragraph->builtin_baseline.has_value() && is_default_builtin_files_registry) { if (std::any_of(core_paragraph->dependencies.begin(), core_paragraph->dependencies.end(),