winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

UpdateFlow.cpp (17498B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "WorkflowBase.h"
      5 #include "DependenciesFlow.h"
      6 #include "InstallFlow.h"
      7 #include "UpdateFlow.h"
      8 #include <winget/ManifestComparator.h>
      9 #include <winget/PinningData.h>
     10 #include <winget/PackageVersionSelection.h>
     11 
     12 using namespace AppInstaller::Repository;
     13 using namespace AppInstaller::Repository::Microsoft;
     14 using namespace AppInstaller::Pinning;
     15 
     16 namespace AppInstaller::CLI::Workflow
     17 {
     18     namespace
     19     {
     20         bool IsUpdateVersionAvailable(const Utility::Version& installedVersion, const Utility::Version& updateVersion)
     21         {
     22             return installedVersion < updateVersion;
     23         }
     24 
     25         void AddToPackageSubContextsIfNotPresent(std::vector<std::unique_ptr<Execution::Context>>& packageSubContexts, std::unique_ptr<Execution::Context> packageContext)
     26         {
     27             for (auto const& existing : packageSubContexts)
     28             {
     29                 if (existing->Get<Execution::Data::Manifest>().Id == packageContext->Get<Execution::Data::Manifest>().Id &&
     30                     existing->Get<Execution::Data::Manifest>().Version == packageContext->Get<Execution::Data::Manifest>().Version &&
     31                     existing->Get<Execution::Data::PackageVersion>()->GetProperty(PackageVersionProperty::SourceIdentifier) == packageContext->Get<Execution::Data::PackageVersion>()->GetProperty(PackageVersionProperty::SourceIdentifier))
     32                 {
     33                     return;
     34                 }
     35             }
     36 
     37             packageSubContexts.emplace_back(std::move(packageContext));
     38         }
     39     }
     40 
     41     void SelectLatestApplicableVersion::operator()(Execution::Context& context) const
     42     {
     43         auto package = context.Get<Execution::Data::Package>();
     44         auto installedPackage = context.Get<Execution::Data::InstalledPackageVersion>();
     45         const bool reportVersionNotFound = m_isSinglePackage;
     46 
     47         bool isUpgrade = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);;
     48         Utility::Version installedVersion;
     49         if (isUpgrade)
     50         {
     51             installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version));
     52         }
     53 
     54         Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, isUpgrade ? installedPackage->GetMetadata() : IPackageVersion::Metadata{}));
     55         bool versionFound = false;
     56         bool installedTypeInapplicable = false;
     57         bool packagePinned = false;
     58 
     59         if (isUpgrade && installedVersion.IsUnknown() && !context.Args.Contains(Execution::Args::Type::IncludeUnknown))
     60         {
     61             // the package has an unknown version and the user did not request to upgrade it anyway
     62             if (reportVersionNotFound)
     63             {
     64                 context.Reporter.Info() << Resource::String::UpgradeUnknownVersionExplanation << std::endl;
     65             }
     66 
     67             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE);
     68         }
     69 
     70         // If we are updating a single package or we got the --include-pinned flag,
     71         // we include packages with Pinning pins
     72         const bool includePinned = m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned);
     73 
     74         PinningData pinningData{ PinningData::Disposition::ReadOnly };
     75         auto evaluator = pinningData.CreatePinStateEvaluator(includePinned ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins, GetInstalledVersion(package));
     76 
     77         // The version keys should have already been sorted by version
     78         auto availableVersions = GetAvailableVersionsForInstalledVersion(package);
     79         const auto& versionKeys = availableVersions->GetVersionKeys();
     80         // Assume that no update versions are applicable
     81         bool upgradeVersionAvailable = false;
     82         for (const auto& key : versionKeys)
     83         {
     84             // Check Applicable Version
     85             if (!isUpgrade || IsUpdateVersionAvailable(installedVersion, Utility::Version(key.Version)))
     86             {
     87                 // The only way to enter this portion of the statement with isUpgrade is if the version is available
     88                 if (isUpgrade)
     89                 {
     90                     AICLI_LOG(CLI, Verbose, << "Updating from [" << installedVersion.ToString() << "] to [" << key.Version << "]");
     91                     upgradeVersionAvailable = true;
     92                 }
     93 
     94                 auto packageVersion = availableVersions->GetVersion(key);
     95 
     96                 // Check if the package is pinned
     97                 PinType pinType = evaluator.EvaluatePinType(packageVersion);
     98                 if (pinType != Pinning::PinType::Unknown)
     99                 {
    100                     AICLI_LOG(CLI, Info, << "Package [" << package->GetProperty(PackageProperty::Id) << " with Version[" << key.Version << "] from Source[" << key.SourceId << "] has a Pin with type[" << ToString(pinType) << "]");
    101                     if (context.Args.Contains(Execution::Args::Type::Force))
    102                     {
    103                         AICLI_LOG(CLI, Info, << "Ignoring pin due to --force argument");
    104                     }
    105                     else
    106                     {
    107                         packagePinned = true;
    108                         continue;
    109                     }
    110                 }
    111 
    112                 auto manifest = packageVersion->GetManifest();
    113 
    114                 // Check applicable Installer
    115                 auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(manifest);
    116                 if (!installer.has_value())
    117                 {
    118                     // If there is at least one installer whose only reason is InstalledType.
    119                     auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType);
    120                     if (onlyInstalledType != inapplicabilities.end())
    121                     {
    122                         installedTypeInapplicable = true;
    123                     }
    124 
    125                     continue;
    126                 }
    127 
    128                 Logging::Telemetry().LogSelectedInstaller(
    129                     static_cast<int>(installer->Arch),
    130                     installer->Url,
    131                     Manifest::InstallerTypeToString(installer->EffectiveInstallerType()),
    132                     Manifest::ScopeToString(installer->Scope),
    133                     installer->Locale);
    134 
    135                 Logging::Telemetry().LogManifestFields(
    136                     manifest.Id,
    137                     manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>(),
    138                     manifest.Version);
    139 
    140                 // Since we already did installer selection, just populate the context Data
    141                 manifest.ApplyLocale(installer->Locale);
    142                 context.Add<Execution::Data::Manifest>(std::move(manifest));
    143                 context.Add<Execution::Data::PackageVersion>(std::move(packageVersion));
    144                 context.Add<Execution::Data::Installer>(std::move(installer));
    145 
    146                 versionFound = true;
    147                 break;
    148             }
    149             else
    150             {
    151                 // Any following versions are not applicable
    152                 break;
    153             }
    154         }
    155 
    156         if (!versionFound)
    157         {
    158             if (reportVersionNotFound)
    159             {
    160                 if (installedTypeInapplicable)
    161                 {
    162                     context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions << std::endl;
    163                 }
    164                 else if (packagePinned)
    165                 {
    166                     context.Reporter.Info() << Resource::String::UpgradeIsPinned << std::endl;
    167                     AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED);
    168                 }
    169                 else if (isUpgrade)
    170                 {
    171                     if (!upgradeVersionAvailable)
    172                     {
    173                         // This is the case when no newer versions are available in a configured source
    174                         context.Reporter.Info() << Resource::String::UpdateNoPackagesFound << std::endl
    175                             << Resource::String::UpdateNoPackagesFoundReason << std::endl;
    176                     }
    177                     else
    178                     {
    179                         // This is the case when newer versions are available in a configured source, but none are applicable due to OS Version, user requirement, etc.
    180                         context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl
    181                             << Resource::String::UpdateNotApplicableReason << std::endl;
    182                     }
    183                 }
    184                 else
    185                 {
    186                     context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl;
    187                 }
    188             }
    189 
    190             AICLI_TERMINATE_CONTEXT(isUpgrade ? APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER);
    191         }
    192     }
    193 
    194     void EnsureUpdateVersionApplicable(Execution::Context& context)
    195     {
    196         auto installedPackage = context.Get<Execution::Data::InstalledPackageVersion>();
    197         Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version));
    198         Utility::Version updateVersion(context.Get<Execution::Data::Manifest>().Version);
    199 
    200         if (!IsUpdateVersionAvailable(installedVersion, updateVersion))
    201         {
    202             context.Reporter.Info() << Resource::String::UpdateNoPackagesFound << std::endl
    203                 << Resource::String::UpdateNoPackagesFoundReason << std::endl;
    204             AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE);
    205         }
    206     }
    207 
    208     void UpdateAllApplicable(Execution::Context& context)
    209     {
    210         const auto& matches = context.Get<Execution::Data::SearchResult>().Matches;
    211         std::vector<std::unique_ptr<Execution::Context>> packageSubContexts;
    212         bool updateAllFoundUpdate = false;
    213         int packagesWithUnknownVersionSkipped = 0;
    214         int packagesThatRequireExplicitSkipped = 0;
    215 
    216         for (const auto& match : matches)
    217         {
    218             // We want to do best effort to update all applicable updates regardless on previous update failure
    219             auto updateContextPtr = context.CreateSubContext();
    220             Execution::Context& updateContext = *updateContextPtr;
    221             auto previousThreadGlobals = updateContext.SetForCurrentThread();
    222             auto installedVersion = GetInstalledVersion(match.Package);
    223 
    224             updateContext.Add<Execution::Data::Package>(match.Package);
    225 
    226             // Filter out packages with unknown installed versions
    227             if (Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() &&
    228                 !context.Args.Contains(Execution::Args::Type::IncludeUnknown))
    229             {
    230                 // we don't know what the package's version is and the user didn't ask to upgrade it anyway.
    231                 AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it has unknown installed version");
    232                 ++packagesWithUnknownVersionSkipped;
    233                 continue;
    234             }
    235 
    236             updateContext <<
    237                 Workflow::GetInstalledPackageVersion <<
    238                 Workflow::ReportExecutionStage(ExecutionStage::Discovery) <<
    239                 SelectLatestApplicableVersion(false);
    240 
    241             if (updateContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE)
    242             {
    243                 continue;
    244             }
    245 
    246             // Filter out packages that require explicit upgrade.
    247             // User-defined pins are handled when selecting the version to use.
    248             auto installedMetadata = updateContext.Get<Execution::Data::InstalledPackageVersion>()->GetMetadata();
    249             auto pinnedState = ConvertToPinTypeEnum(installedMetadata[PackageVersionMetadata::PinnedState]);
    250             if (pinnedState == PinType::PinnedByManifest)
    251             {
    252                 // Note that for packages pinned by the manifest
    253                 // this does not consider whether the update to be installed has
    254                 // RequireExplicitUpgrade. While this has the downside of not working with
    255                 // packages installed from another source, it ensures consistency with the
    256                 // list of available updates (there we don't have the selected installer)
    257                 // and at most we will update each package like this once.
    258                 AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it requires explicit upgrade");
    259                 ++packagesThatRequireExplicitSkipped;
    260                 continue;
    261             }
    262 
    263             updateAllFoundUpdate = true;
    264 
    265             AddToPackageSubContextsIfNotPresent(packageSubContexts, std::move(updateContextPtr));
    266         }
    267 
    268         if (updateAllFoundUpdate)
    269         {
    270             context.Add<Execution::Data::PackageSubContexts>(std::move(packageSubContexts));
    271             context.Reporter.Info() << std::endl;
    272 
    273             ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None;
    274             if (Settings::User().Get<Settings::Setting::InstallSkipDependencies>() || context.Args.Contains(Execution::Args::Type::SkipDependencies))
    275             {
    276                 flags = ProcessMultiplePackages::Flags::IgnoreDependencies;
    277             }
    278 
    279             context <<
    280                 ProcessMultiplePackages(
    281                     Resource::String::PackageRequiresDependencies,
    282                     APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE,
    283                     flags,
    284                     { APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE });
    285         }
    286 
    287         if (packagesWithUnknownVersionSkipped > 0)
    288         {
    289             AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version");
    290             context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl;
    291         }
    292 
    293         if (packagesThatRequireExplicitSkipped > 0)
    294         {
    295             AICLI_LOG(CLI, Info, << packagesThatRequireExplicitSkipped << " package(s) skipped due to requiring explicit upgrade");
    296             context.Reporter.Info() << Resource::String::UpgradeRequireExplicitCount(packagesThatRequireExplicitSkipped) << std::endl;
    297         }
    298     }
    299 
    300     void SelectSinglePackageVersionForInstallOrUpgrade::operator()(Execution::Context& context) const
    301     {
    302         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_operationType != OperationType::Install && m_operationType != OperationType::Upgrade);
    303 
    304         context <<
    305             HandleSearchResultFailures <<
    306             EnsureOneMatchFromSearchResult(m_operationType) <<
    307             GetInstalledPackageVersion;
    308 
    309         if ( m_operationType != OperationType::Upgrade && 
    310             context.Contains(Execution::Data::InstalledPackageVersion) &&
    311             context.Get<Execution::Data::InstalledPackageVersion>() != nullptr )
    312         {
    313             if (context.Args.Contains(Execution::Args::Type::NoUpgrade))
    314             {
    315                 AICLI_LOG(CLI, Warning, << "Found installed package, exiting installation.");
    316                 context.Reporter.Warn() << Resource::String::PackageAlreadyInstalled << std::endl;
    317                 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED);
    318             }
    319             else
    320             {
    321                 AICLI_LOG(CLI, Info, << "Found installed package, converting to upgrade flow");
    322                 context.Reporter.Info() << Execution::ConvertToUpgradeFlowEmphasis << Resource::String::ConvertInstallFlowToUpgrade << std::endl;
    323                 context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate);
    324                 m_operationType = OperationType::Upgrade;
    325             }
    326         }
    327 
    328         if (context.Args.Contains(Execution::Args::Type::Version))
    329         {
    330             // If version specified, use the version and verify applicability
    331             context << GetManifestFromPackage(/* considerPins */ true);
    332 
    333             if (m_operationType == OperationType::Upgrade && !m_allowDowngrade)
    334             {
    335                 context << EnsureUpdateVersionApplicable;
    336             }
    337 
    338             context << SelectInstaller;
    339         }
    340         else
    341         {
    342             // Iterate through available versions to find latest applicable version.
    343             // This step also populates Manifest and Installer in context data.
    344             context << SelectLatestApplicableVersion(true);
    345         }
    346 
    347         context << EnsureApplicableInstaller;
    348     }
    349 
    350     void InstallOrUpgradeSinglePackage::operator()(Execution::Context& context) const
    351     {
    352         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_operationType != OperationType::Install && m_operationType != OperationType::Upgrade);
    353 
    354         context <<
    355             SearchSourceForSingle <<
    356             SelectSinglePackageVersionForInstallOrUpgrade(m_operationType) <<
    357             InstallSinglePackage;
    358     }
    359 }