Skip to content

Commit

Permalink
Implement package pinning (#2813)
Browse files Browse the repository at this point in the history
  • Loading branch information
florelis authored Feb 9, 2023
1 parent 78aca69 commit b21389c
Show file tree
Hide file tree
Showing 48 changed files with 1,649 additions and 364 deletions.
4 changes: 3 additions & 1 deletion src/AppInstallerCLICore/Argument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,10 @@ namespace AppInstaller::CLI
return { type, "all"_liv, 'r', "recurse"_liv, ArgTypeCategory::MultiplePackages };
case Execution::Args::Type::IncludeUnknown:
return { type, "include-unknown"_liv, 'u', "unknown"_liv };
case Execution::Args::Type::IncludePinned:
return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext };
case Execution::Args::Type::UninstallPrevious:
return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior };
return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext };

// Show command
case Execution::Args::Type::ListVersions:
Expand Down
2 changes: 1 addition & 1 deletion src/AppInstallerCLICore/Commands/ShowCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ namespace AppInstaller::CLI
else
{
context <<
Workflow::GetManifest <<
Workflow::GetManifest(/* considerPins */ false) <<
Workflow::ReportManifestIdentity <<
Workflow::SelectInstaller <<
Workflow::ShowManifestInfo;
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/Commands/UpgradeCommand.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ namespace AppInstaller::CLI
Argument::ForType(Execution::Args::Type::CustomHeader),
Argument{ Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag },
Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag },
Argument{ Args::Type::IncludePinned, Resource::String::IncludePinnedArgumentDescription, ArgumentType::Flag},
Argument::ForType(Args::Type::UninstallPrevious),
Argument::ForType(Args::Type::Force),
};
Expand Down
1 change: 1 addition & 0 deletions src/AppInstallerCLICore/ExecutionArgs.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ namespace AppInstaller::CLI::Execution
// Upgrade command
All, // Used in Update command to update all installed packages to latest
IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions
IncludePinned, // Used in Upgrade command to allow upgrades to pinned packages (only for pinning type of pins)
UninstallPrevious, // Used in Upgrade command to override the default manifest behavior to UninstallPrevious

// Show command
Expand Down
6 changes: 6 additions & 0 deletions src/AppInstallerCLICore/Resources.h
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed);
WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided);
WINGET_DEFINE_RESOURCE_STRINGID(InstallAndUpgradeCommandsReportDependencies);
Expand Down Expand Up @@ -245,6 +246,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt);
WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled);
WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies);
WINGET_DEFINE_RESOURCE_STRINGID(PackageIsPinned);
WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError);
WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription);
Expand All @@ -262,6 +264,7 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinRemovedSuccessfully);
WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful);
Expand Down Expand Up @@ -433,10 +436,13 @@ namespace AppInstaller::CLI::Resource
WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription);
WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockingPinCount);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount);
WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation);
Expand Down
13 changes: 12 additions & 1 deletion src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,18 @@ namespace AppInstaller::CLI::Workflow
const auto& package = match.Package;
auto packageId = package->GetProperty(PackageProperty::Id);
m_nodePackageInstalledVersion = package->GetInstalledVersion();
m_nodePackageLatestVersion = package->GetLatestAvailableVersion();

PinBehavior pinBehavior;
if (m_context.Args.Contains(Execution::Args::Type::Force))
{
pinBehavior = PinBehavior::IgnorePins;
}
else
{
pinBehavior = m_context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins;
}

m_nodePackageLatestVersion = package->GetLatestAvailableVersion(pinBehavior);

if (m_nodePackageInstalledVersion && dependencyNode.IsVersionOk(Utility::Version(m_nodePackageInstalledVersion->GetProperty(PackageVersionProperty::Version))))
{
Expand Down
4 changes: 2 additions & 2 deletions src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,13 @@ namespace AppInstaller::CLI::Workflow
{
if (!checkVersion)
{
return package->GetLatestAvailableVersion();
return package->GetLatestAvailableVersion(PinBehavior::IgnorePins);
}

auto availablePackageVersion = package->GetAvailableVersion({ "", version, channel });
if (!availablePackageVersion)
{
availablePackageVersion = package->GetLatestAvailableVersion();
availablePackageVersion = package->GetLatestAvailableVersion(PinBehavior::IgnorePins);
if (availablePackageVersion)
{
// Warn installed version is not available.
Expand Down
175 changes: 121 additions & 54 deletions src/AppInstallerCLICore/Workflows/PinFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,27 @@ namespace AppInstaller::CLI::Workflow
{
auto package = context.Get<Execution::Data::Package>();
std::vector<Pinning::Pin> pins;

// TODO: We should support querying the multiple sources for a package, instead of just one
auto availableVersion = package->GetLatestAvailableVersion();
std::set<std::string> sources;

auto pinningIndex = context.Get<Execution::Data::PinningIndex>();
auto pin = pinningIndex->GetPin({
availableVersion->GetProperty(PackageVersionProperty::Id).get(),
availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() });
if (pin)

auto packageVersionKeys = package->GetAvailableVersionKeys();
for (const auto& versionKey : packageVersionKeys)

{
pins.emplace_back(std::move(pin.value()));
auto availableVersion = package->GetAvailableVersion(versionKey);
Pinning::PinKey pinKey{
availableVersion->GetProperty(PackageVersionProperty::Id).get(),
availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() };

if (sources.insert(pinKey.SourceId).second)
{
auto pin = pinningIndex->GetPin(pinKey);
if (pin)
{
pins.emplace_back(std::move(pin.value()));
}
}
}

context.Add<Execution::Data::Pins>(std::move(pins));
Expand All @@ -78,49 +88,73 @@ namespace AppInstaller::CLI::Workflow
auto package = context.Get<Execution::Data::Package>();
auto installedVersion = context.Get<Execution::Data::InstalledPackageVersion>();

std::vector<Pinning::Pin> pins;

// TODO: We should support querying the multiple sources for a package, instead of just one
auto availableVersion = package->GetLatestAvailableVersion();

Pinning::PinKey pinKey{
availableVersion->GetProperty(PackageVersionProperty::Id).get(),
availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() };
auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId);
AICLI_LOG(CLI, Info, << "Adding pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]");
auto installedVersionString = installedVersion->GetProperty(PackageVersionProperty::Version);

std::vector<Pinning::Pin> pinsToAddOrUpdate;
std::set<std::string> sources;

auto pinningIndex = context.Get<Execution::Data::PinningIndex>();
auto existingPin = pinningIndex->GetPin(pinKey);

if (existingPin)
auto packageVersionKeys = package->GetAvailableVersionKeys();
for (const auto& versionKey : packageVersionKeys)

{
// Pin already exists.
// If it is the same, we do nothing. If it is different, check for the --force arg
if (pin == existingPin)
auto availableVersion = package->GetAvailableVersion(versionKey);
Pinning::PinKey pinKey{
availableVersion->GetProperty(PackageVersionProperty::Id).get(),
availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() };

if (!sources.insert(pinKey.SourceId).second)
{
AICLI_LOG(CLI, Info, << "Pin already exists");
context.Reporter.Info() << Resource::String::PinAlreadyExists << std::endl;
return;
// We already considered the pin for this source
continue;
}

AICLI_LOG(CLI, Info, << "Another pin already exists for the package");
if (context.Args.Contains(Execution::Args::Type::Force))
auto pin = CreatePin(context, pinKey.PackageId, pinKey.SourceId);
AICLI_LOG(CLI, Info, << "Evaluating pin with type " << ToString(pin.GetType()) << " for package [" << pin.GetPackageId() << "] from source [" << pin.GetSourceId() << "]");

auto existingPin = pinningIndex->GetPin(pinKey);

if (existingPin)
{
AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument");
context.Reporter.Warn() << Resource::String::PinExistsOverwriting << std::endl;
pinningIndex->UpdatePin(pin);
auto packageName = availableVersion->GetProperty(PackageVersionProperty::Name);

// Pin already exists.
// If it is the same, we do nothing. If it is different, check for the --force arg
if (pin == existingPin)
{
AICLI_LOG(CLI, Info, << "Pin already exists");
context.Reporter.Info() << Resource::String::PinAlreadyExists(packageName) << std::endl;
continue;
}

AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId);
if (context.Args.Contains(Execution::Args::Type::Force))
{
AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument");
context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageName) << std::endl;
pinsToAddOrUpdate.push_back(std::move(pin));
}
else
{
context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageName) << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS);
}
}
else
{
context.Reporter.Error() << Resource::String::PinExistsUseForceArg << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS);
pinsToAddOrUpdate.push_back(std::move(pin));
}
}
else

if (!pinsToAddOrUpdate.empty())
{
pinningIndex->AddPin(pin);
AICLI_LOG(CLI, Info, << "Finished adding pin");
for (const auto& pin : pinsToAddOrUpdate)

{
pinningIndex->AddOrUpdatePin(pin);
}

context.Reporter.Info() << Resource::String::PinAdded << std::endl;
}
}
Expand All @@ -129,23 +163,42 @@ namespace AppInstaller::CLI::Workflow
{
auto package = context.Get<Execution::Data::Package>();
std::vector<Pinning::Pin> pins;

// TODO: We should support querying the multiple sources for a package, instead of just one
auto availableVersion = package->GetLatestAvailableVersion();
std::set<std::string> sources;

auto pinningIndex = context.Get<Execution::Data::PinningIndex>();
Pinning::PinKey pinKey{
availableVersion->GetProperty(PackageVersionProperty::Id).get(),
availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() };
AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]");
if (!pinningIndex->GetPin(pinKey))
bool pinExists = false;

// Note that if a source was specified in the command line,
// that will be the only one we get version keys from.
// So, we remove pins from all sources unless one was provided.
auto packageVersionKeys = package->GetAvailableVersionKeys();
for (const auto& versionKey : packageVersionKeys)

{
auto availableVersion = package->GetAvailableVersion(versionKey);
Pinning::PinKey pinKey{
availableVersion->GetProperty(PackageVersionProperty::Id).get(),
availableVersion->GetProperty(PackageVersionProperty::SourceIdentifier).get() };

if (sources.insert(pinKey.SourceId).second)
{
if (pinningIndex->GetPin(pinKey))
{
AICLI_LOG(CLI, Info, << "Removing pin for package [" << pinKey.PackageId << "] from source [" << pinKey.SourceId << "]");
pinningIndex->RemovePin(pinKey);
pinExists = true;
}
}
}

if (!pinExists)
{
AICLI_LOG(CLI, Warning, << "Pin does not exist");
context.Reporter.Warn() << Resource::String::PinDoesNotExist(pinKey.PackageId) << std::endl;
context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl;
AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST);
}

pinningIndex->RemovePin(pinKey);
context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl;
}

void ReportPins(Execution::Context& context)
Expand All @@ -157,7 +210,13 @@ namespace AppInstaller::CLI::Workflow
return;
}

// TODO: Use package and source names
// Get a mapping of source IDs to names so that we can show something nicer
std::map<std::string, std::string> sourceNames;
for (const auto& source : Repository::Source::GetCurrentSources())
{
sourceNames[source.Identifier] = source.Name;
}

Execution::TableOutput<4> table(context.Reporter,
{
Resource::String::SearchId,
Expand All @@ -168,12 +227,11 @@ namespace AppInstaller::CLI::Workflow

for (const auto& pin : pins)
{
// TODO: Avoid these conversions to string
table.OutputLine({
pin.GetPackageId(),
std::string{ pin.GetSourceId() },
pin.GetGatedVersion().ToString(),
sourceNames[pin.GetSourceId()],
std::string{ ToString(pin.GetType()) },
pin.GetGatedVersion().ToString(),
});
}

Expand Down Expand Up @@ -216,14 +274,23 @@ namespace AppInstaller::CLI::Workflow
const auto& source = context.Get<Execution::Data::Source>();

std::vector<Pinning::Pin> matchingPins;
std::copy_if(pins.begin(), pins.end(), std::back_inserter(matchingPins), [&](Pinning::Pin pin) {
// TODO: Filter to source
for (const auto& pin : pins)

{
SearchRequest searchRequest;
searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pin.GetPackageId());
auto searchResult = source.Search(searchRequest);

return !searchResult.Matches.empty();
});
// Ensure the match comes from the right source
for (const auto& match : searchResult.Matches)
{
auto availableVersion = match.Package->GetAvailableVersion({ pin.GetSourceId(), "", "" });
if (availableVersion)
{
matchingPins.push_back(pin);
}
}
}

context.Add<Execution::Data::Pins>(std::move(matchingPins));
}
Expand Down
4 changes: 2 additions & 2 deletions src/AppInstallerCLICore/Workflows/PinFlow.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace AppInstaller::CLI::Workflow
// There may be several if a package is available from multiple sources.
// Required Args: None
// Inputs: PinningIndex, Package
// Outputs Pins
// Outputs: Pins
void SearchPin(Execution::Context& context);

// Adds a pin for the current package.
Expand Down Expand Up @@ -57,7 +57,7 @@ namespace AppInstaller::CLI::Workflow
// Outputs: None
void ResetAllPins(Execution::Context& context);

// Updates the list of pins to include only those matching the current open source
// Updates the list of pins to include only those matching the current open source.
// Required Args: None
// Inputs: Pins, Source
// Outputs: None
Expand Down
Loading

0 comments on commit b21389c

Please sign in to comment.