From 291651fb3ffca3171e483979c5f2a4eab223144d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Flor=20Chac=C3=B3n?= <14323496+florelis@users.noreply.github.com> Date: Thu, 8 Dec 2022 13:24:22 -0800 Subject: [PATCH] Add empty `pin` command (#2733) --- doc/Settings.md | 14 +- .../AppInstallerCLICore.vcxproj | 3 + .../AppInstallerCLICore.vcxproj.filters | 9 + .../Commands/PinCommand.cpp | 243 ++++++++++++++++++ src/AppInstallerCLICore/Commands/PinCommand.h | 90 +++++++ .../Commands/RootCommand.cpp | 2 + src/AppInstallerCLICore/ExecutionArgs.h | 4 + src/AppInstallerCLICore/Resources.h | 13 + src/AppInstallerCLICore/Workflows/PinFlow.h | 8 + .../Shared/Strings/en-us/winget.resw | 41 +++ .../ExperimentalFeature.cpp | 4 + .../Public/winget/ExperimentalFeature.h | 1 + .../Public/winget/UserSettings.h | 2 + src/AppInstallerCommonCore/UserSettings.cpp | 1 + 14 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 src/AppInstallerCLICore/Commands/PinCommand.cpp create mode 100644 src/AppInstallerCLICore/Commands/PinCommand.h create mode 100644 src/AppInstallerCLICore/Workflows/PinFlow.h diff --git a/doc/Settings.md b/doc/Settings.md index b2a7cd98f9..f0d09d3b2d 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -253,7 +253,8 @@ You can enable the feature as shown below. "openLogsArgument": true }, ``` -### Dependencies + +### dependencies Experimental feature with the aim of managing dependencies, as of now it only shows package dependency information. You can enable the feature as shown below. @@ -262,3 +263,14 @@ Experimental feature with the aim of managing dependencies, as of now it only sh "dependencies": true }, ``` + +### pinning + +This feature enables the ability to pin packages to prevent the Windows Package Manager from updating them. +You can enable the feature as shown below. + +```json + "experimentalFeatures": { + "pinning": true + }, +``` \ No newline at end of file diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index a78da26639..effaa5d8b7 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -249,6 +249,7 @@ + @@ -283,6 +284,7 @@ + @@ -299,6 +301,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 5ddf0b00df..53e912fc82 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -188,6 +188,12 @@ Header Files + + Commands + + + Workflows + @@ -343,6 +349,9 @@ Source Files + + Source Files + diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp new file mode 100644 index 0000000000..5cdedb26b0 --- /dev/null +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -0,0 +1,243 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/PinFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::Utility::literals; + using namespace std::string_view_literals; + + static constexpr std::string_view s_PinCommand_HelpLink = "https://aka.ms/winget-command-pin"sv; + + std::vector> PinCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + Resource::LocString PinCommand::ShortDescription() const + { + return { Resource::String::PinCommandShortDescription }; + } + + Resource::LocString PinCommand::LongDescription() const + { + return { Resource::String::PinCommandLongDescription }; + } + + std::string PinCommand::HelpLink() const + { + return std::string{ s_PinCommand_HelpLink }; + } + + void PinCommand::ExecuteInternal(Execution::Context& context) const + { + OutputHelp(context.Reporter); + } + + std::vector PinAddCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument{ "version"_liv, 'v', Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::Force), + Argument{ "blocking"_liv, Argument::NoAlias, Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString PinAddCommand::ShortDescription() const + { + return { Resource::String::PinAddCommandShortDescription }; + } + + Resource::LocString PinAddCommand::LongDescription() const + { + return { Resource::String::PinAddCommandLongDescription }; + } + + void PinAddCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + switch (valueType) + { + case Args::Type::Query: + case Args::Type::Id: + case Args::Type::Name: + case Args::Type::Moniker: + case Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + break; + } + } + + std::string PinAddCommand::HelpLink() const + { + return std::string{ s_PinCommand_HelpLink }; + } + + void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::GatedVersion) && execArgs.Contains(Execution::Args::Type::BlockingPin)) + { + throw CommandException(Resource::String::BothGatedVersionAndBlockingFlagProvided); + } + + } + + void PinAddCommand::ExecuteInternal(Execution::Context& context) const + { + // TODO + Command::ExecuteInternal(context); + } + + std::vector PinRemoveCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AcceptSourceAgreements), + }; + } + + Resource::LocString PinRemoveCommand::ShortDescription() const + { + return { Resource::String::PinRemoveCommandShortDescription }; + } + + Resource::LocString PinRemoveCommand::LongDescription() const + { + return { Resource::String::PinRemoveCommandLongDescription }; + } + + void PinRemoveCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + switch (valueType) + { + case Args::Type::Query: + case Args::Type::Id: + case Args::Type::Name: + case Args::Type::Moniker: + case Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + break; + } + } + + std::string PinRemoveCommand::HelpLink() const + { + return std::string{ s_PinCommand_HelpLink }; + } + + void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const + { + // TODO + Command::ExecuteInternal(context); + } + + std::vector PinListCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AcceptSourceAgreements), + }; + } + + Resource::LocString PinListCommand::ShortDescription() const + { + return { Resource::String::PinListCommandShortDescription }; + } + + Resource::LocString PinListCommand::LongDescription() const + { + return { Resource::String::PinListCommandLongDescription }; + } + + void PinListCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + switch (valueType) + { + case Args::Type::Query: + case Args::Type::Id: + case Args::Type::Name: + case Args::Type::Moniker: + case Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + break; + } + } + + std::string PinListCommand::HelpLink() const + { + return std::string{ s_PinCommand_HelpLink }; + } + + void PinListCommand::ExecuteInternal(Execution::Context& context) const + { + // TODO + Command::ExecuteInternal(context); + } + + std::vector PinResetCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Force), + }; + } + + Resource::LocString PinResetCommand::ShortDescription() const + { + return { Resource::String::PinResetCommandShortDescription }; + } + + Resource::LocString PinResetCommand::LongDescription() const + { + return { Resource::String::PinResetCommandLongDescription }; + } + + std::string PinResetCommand::HelpLink() const + { + return std::string{ s_PinCommand_HelpLink }; + } + + void PinResetCommand::ExecuteInternal(Execution::Context& context) const + { + // TODO + Command::ExecuteInternal(context); + } +} diff --git a/src/AppInstallerCLICore/Commands/PinCommand.h b/src/AppInstallerCLICore/Commands/PinCommand.h new file mode 100644 index 0000000000..8740971b9d --- /dev/null +++ b/src/AppInstallerCLICore/Commands/PinCommand.h @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include + +namespace AppInstaller::CLI +{ + struct PinCommand final : public Command + { + PinCommand(std::string_view parent) : Command("pin", {} /* aliases */, parent, Settings::ExperimentalFeature::Feature::Pinning) {} + + std::vector> GetCommands() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinAddCommand final : public Command + { + PinAddCommand(std::string_view parent) : Command("add", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + std::string HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinRemoveCommand final : public Command + { + PinRemoveCommand(std::string_view parent) : Command("remove", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinListCommand final : public Command + { + PinListCommand(std::string_view parent) : Command("list", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinResetCommand final : public Command + { + PinResetCommand(std::string_view parent) : Command("reset", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + std::string HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index f07358655e..ebf081e4df 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -18,6 +18,7 @@ #include "CompleteCommand.h" #include "ExportCommand.h" #include "ImportCommand.h" +#include "PinCommand.h" #include "Resources.h" #include "TableOutput.h" @@ -127,6 +128,7 @@ namespace AppInstaller::CLI std::make_unique(FullName()), std::make_unique(FullName()), std::make_unique(FullName()), + std::make_unique(FullName()), }); } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index fd3d196b72..d0df99a98f 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -87,6 +87,10 @@ namespace AppInstaller::CLI::Execution // Show command ListVersions, // Used in Show command to list all available versions of an app + // Pin command + GatedVersion, // Differs from Version in that this supports wildcards + BlockingPin, + // Common arguments NoVT, // Disable VirtualTerminal outputs RetroStyle, // Makes progress display as retro diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 9ac6f3d06d..1122c02259 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -36,6 +36,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades); + WINGET_DEFINE_RESOURCE_STRINGID(BothGatedVersionAndBlockingFlagProvided); WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided); WINGET_DEFINE_RESOURCE_STRINGID(BothPurgeAndPreserveFlagsProvided); WINGET_DEFINE_RESOURCE_STRINGID(Cancelled); @@ -96,6 +97,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); @@ -237,6 +239,17 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled); WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesDisabled); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesEnabled); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h new file mode 100644 index 0000000000..504ed5782e --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ +} diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 947caa549a..73044e944f 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1485,4 +1485,45 @@ Please specify one of them using the --source option to proceed. Cannot disable %1. This setting is controlled by policy. For more information contact your system administrator. {Locked="%1"} The value will be replaced with the feature name + + Add a new pin. A pin can limit the Windows Package Manager from updating a package to specific ranges of versions, or it can prevent it from updating the package altogether. A pinned package may still update on its own and be updated from outside the Windows Package Manager. By default, a pinned package can be updated by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. + {Locked{"'upgrade'"} Locked{"--include-pinned"} Locked{"winget upgrade --all"} + + + Add a new pin + + + Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from updating a package to specific ranges of versions, or it can prevent it from updating the package altogether. A pinned package may still update on its own and be updated from outside the Windows Package Manager. + + + Manage package pins + + + List all current pins, or full details of a specific pin. + + + List current pins + + + Remove a specific package pin. + + + Remove a package pin + + + Reset all existing pins. + + + Reset pins + + + Both version and '--blocking' arguments provided + {Locked="--blocking"} + + + Version to which to pin the package. The wildcard '*' can be used as the last version part + + + Block from updating until the pin is removed, preventing override arguments + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp index 49067ae966..22bcec667a 100644 --- a/src/AppInstallerCommonCore/ExperimentalFeature.cpp +++ b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -46,6 +46,8 @@ namespace AppInstaller::Settings return userSettings.Get(); case ExperimentalFeature::Feature::OpenLogsArgument: return userSettings.Get(); + case ExperimentalFeature::Feature::Pinning: + return userSettings.Get(); default: THROW_HR(E_UNEXPECTED); } @@ -81,6 +83,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Zip Installation", "zipInstall", "https://aka.ms/winget-settings", Feature::ZipInstall }; case Feature::OpenLogsArgument: return ExperimentalFeature{ "Open Logs Argument", "openLogsArgument", "https://aka.ms/winget-settings", Feature::OpenLogsArgument }; + case Feature::Pinning: + return ExperimentalFeature{ "Package Pinning", "pinning", "https://aka.ms/winget-settings", Feature::Pinning}; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h index 76ac6e7523..f34b24d2c3 100644 --- a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h +++ b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -25,6 +25,7 @@ namespace AppInstaller::Settings DirectMSI = 0x2, ZipInstall = 0x4, OpenLogsArgument = 0x8, + Pinning = 0x10, Max, // This MUST always be after all experimental features // Features listed after Max will not be shown with the features command diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index b6e4a9b2cf..ec14844426 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -79,6 +79,7 @@ namespace AppInstaller::Settings EFDirectMSI, EFZipInstall, EFOpenLogsArgument, + EFPinning, // Telemetry TelemetryDisable, // Install behavior @@ -148,6 +149,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFZipInstall, bool, bool, false, ".experimentalFeatures.zipInstall"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFOpenLogsArgument, bool, bool, false, ".experimentalFeatures.openLogsArgument"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFPinning, bool, bool, false, ".experimentalFeatures.pinning"sv); // Telemetry SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); // Install behavior diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index ac842a19e6..c97d086868 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -249,6 +249,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) WINGET_VALIDATE_PASS_THROUGH(EFZipInstall) WINGET_VALIDATE_PASS_THROUGH(EFOpenLogsArgument) + WINGET_VALIDATE_PASS_THROUGH(EFPinning) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump)