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 }