diff --git a/azure-pipelines/e2e-specs/autocomplete-posh-vcpkg.Tests.ps1 b/azure-pipelines/e2e-specs/autocomplete-posh-vcpkg.Tests.ps1 index d236274704..8b4851e6fe 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/include/vcpkg/base/message-data.inc.h b/include/vcpkg/base/message-data.inc.h index f947f365ea..b35e5f7736 100644 --- a/include/vcpkg/base/message-data.inc.h +++ b/include/vcpkg/base/message-data.inc.h @@ -341,6 +341,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, + (), + "commit is a git commit here", + "No package that would have been installed was updated between the two commits") DECLARE_MESSAGE(BaselineGitShowFailed, (msg::commit_sha), "", @@ -599,6 +603,11 @@ 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(CmdBaselineDiffSynopsis, + (), + "", + "Computes the set of packages installed for different baselines and compares the versions that " + "would be installed") DECLARE_MESSAGE(CmdBootstrapStandaloneSynopsis, (), "", "Bootstraps a vcpkg root from only a vcpkg binary") DECLARE_MESSAGE(CmdBuildExternalExample1, (), @@ -1066,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, (), "", @@ -2180,6 +2190,7 @@ DECLARE_MESSAGE(MissingDependency, (msg::spec, msg::package_name), "", "Package {spec} is installed, but dependency {package_name} is not.") +DECLARE_MESSAGE(MissingManifestFile, (), "", "Could not find manifest file (vcpkg.json file)") DECLARE_MESSAGE(MissingOption, (msg::option), "", "This command requires --{option}") DECLARE_MESSAGE(MissingOrInvalidIdentifer, (), "", "missing or invalid identifier") DECLARE_MESSAGE(MissingPortSuggestPullRequest, @@ -2870,6 +2881,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, 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/locales/messages.json b/locales/messages.json index d69bcc531e..1502d99369 100644 --- a/locales/messages.json +++ b/locales/messages.json @@ -214,6 +214,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 package that would have been installed was updated between the two commits", + "_BaselineDiffNoChange.comment": "commit is a git commit here", "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", @@ -346,6 +348,7 @@ "CmdAddVersionOptSkipVersionFormatChk": "Skips the version format check", "CmdAddVersionOptVerbose": "Prints success messages rather than only errors", "CmdAddVersionSynopsis": "Adds a version to the version database", + "CmdBaselineDiffSynopsis": "Computes the set of packages installed for different baselines and compares the versions that would be installed", "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", @@ -613,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.", @@ -1183,6 +1187,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": "Could not find manifest file (vcpkg.json file)", "MissingOption": "This command requires --{option}", "_MissingOption.comment": "An example of {option} is editable.", "MissingOrInvalidIdentifer": "missing or invalid identifier", @@ -1492,6 +1497,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:", diff --git a/src/vcpkg/commands.baseline-diff.cpp b/src/vcpkg/commands.baseline-diff.cpp new file mode 100644 index 0000000000..9b6924d0b8 --- /dev/null +++ b/src/vcpkg/commands.baseline-diff.cpp @@ -0,0 +1,184 @@ +#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; + } + + void check_for_valid_sha(StringView sha) + { + if (sha.size() < 7 || !Util::all_of(sha, ParserBase::is_hex_digit_lower)) + { + Checks::msg_exit_with_error(VCPKG_LINE_INFO, msgInvalidCommitId, msg::commit_sha = sha); + } + } + +} // unnamed namespace + +namespace vcpkg +{ + constexpr CommandMetadata CommandBaselineDiffMetadata{ + "x-baseline-diff", + msgCmdBaselineDiffSynopsis, + {"vcpkg x-baseline-diff ", + "vcpkg x-baseline-diff $(git rev-parse 2026.02.27) $(git rev-parse 2026.03.18)"}, + Undocumented, + AutocompletePriority::Public, + 2, + 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); + check_for_valid_sha(options.command_arguments.at(0)); + check_for_valid_sha(options.command_arguments.at(1)); + + auto& fs = paths.get_filesystem(); + InstalledDatabaseLock installed_lock{fs, paths.installed(), 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(); + bool is_default_builtin = configuration.instantiate_registry_set(paths)->is_default_builtin_registry(); + auto manifest_scf = parse_manifest_scf_or_exit(*manifest, paths, is_default_builtin); + + 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 (int i = 0; i < 2; ++i) + { + if (auto default_reg = configuration.config.default_reg.get()) + { + default_reg->baseline = options.command_arguments.at(i); + } + else + { + RegistryConfig synthesized_registry; + synthesized_registry.kind = JsonIdBuiltin.to_string(); + synthesized_registry.baseline = options.command_arguments.at(i); + configuration.config.default_reg.emplace(synthesized_registry); + } + + auto registry_set = configuration.instantiate_registry_set(paths); + auto verprovider = make_versioned_portfile_provider(*registry_set); + auto baseprovider = make_baseline_provider(*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},