ManifestComparator.cpp (41986B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include <winget/ManifestComparator.h> 5 #include <AppInstallerLogging.h> 6 #include <winget/UserSettings.h> 7 #include <winget/Runtime.h> 8 #include <winget/Locale.h> 9 10 using namespace AppInstaller::Manifest; 11 12 namespace AppInstaller::Manifest 13 { 14 std::ostream& operator<<(std::ostream& out, const ManifestInstaller& installer) 15 { 16 return out << '[' << 17 AppInstaller::Utility::ToString(installer.Arch) << ',' << 18 AppInstaller::Manifest::InstallerTypeToString(installer.EffectiveInstallerType()) << ',' << 19 AppInstaller::Manifest::ScopeToString(installer.Scope) << ',' << 20 installer.Locale << ']'; 21 } 22 } 23 24 namespace AppInstaller::Manifest 25 { 26 namespace 27 { 28 struct PortableInstallFilter : public details::FilterField 29 { 30 PortableInstallFilter() : details::FilterField("Portable Install") {} 31 32 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 33 { 34 // Unvirtualized resources restricted capability is only supported for >= 10.0.18362 35 // TODO: Add support for OS versions that don't support virtualization. 36 if (installer.EffectiveInstallerType() == InstallerTypeEnum::Portable && !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version("10.0.18362"))) 37 { 38 return InapplicabilityFlags::OSVersion; 39 } 40 41 return InapplicabilityFlags::None; 42 } 43 44 std::string ExplainInapplicable(const ManifestInstaller&) override 45 { 46 std::string result = "Current OS is lower than supported MinOSVersion (10.0.18362) for Portable install"; 47 return result; 48 } 49 }; 50 51 struct OSVersionFilter : public details::FilterField 52 { 53 OSVersionFilter() : details::FilterField("OS Version") {} 54 55 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 56 { 57 if (installer.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion))) 58 { 59 return InapplicabilityFlags::None; 60 } 61 62 return InapplicabilityFlags::OSVersion; 63 } 64 65 std::string ExplainInapplicable(const ManifestInstaller& installer) override 66 { 67 std::string result = "Current OS is lower than MinOSVersion "; 68 result += installer.MinOSVersion; 69 return result; 70 } 71 }; 72 73 struct MachineArchitectureComparator : public details::ComparisonField 74 { 75 MachineArchitectureComparator() : details::ComparisonField("Machine Architecture") {} 76 77 MachineArchitectureComparator(std::vector<Utility::Architecture> allowedArchitectures) : 78 details::ComparisonField("Machine Architecture"), m_allowedArchitectures(std::move(allowedArchitectures)) 79 { 80 AICLI_LOG(CLI, Verbose, << "Architecture Comparator created with allowed architectures: " << Utility::ConvertContainerToString(m_allowedArchitectures, Utility::ToString)); 81 } 82 83 static std::unique_ptr<MachineArchitectureComparator> Create(const ManifestComparator::Options& options) 84 { 85 if (!options.AllowedArchitectures.empty()) 86 { 87 // If the incoming data contains elements, we will use them to construct a final allowed list. 88 // The algorithm is to take elements until we find Unknown, which indicates that any architecture is 89 // acceptable at this point. The system supported set of architectures will then be placed at the end. 90 std::vector<Utility::Architecture> result; 91 bool addRemainingApplicableArchitectures = false; 92 93 for (Utility::Architecture architecture : options.AllowedArchitectures) 94 { 95 if (architecture == Utility::Architecture::Unknown) 96 { 97 addRemainingApplicableArchitectures = true; 98 break; 99 } 100 101 // If the architecture is applicable and not already in our result set... 102 if ((options.SkipApplicabilityCheck || Utility::IsApplicableArchitecture(architecture) != Utility::InapplicableArchitecture) && 103 Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) 104 { 105 result.push_back(architecture); 106 } 107 } 108 109 if (addRemainingApplicableArchitectures) 110 { 111 for (Utility::Architecture architecture : Utility::GetApplicableArchitectures()) 112 { 113 if (Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) 114 { 115 result.push_back(architecture); 116 } 117 } 118 } 119 120 return std::make_unique<MachineArchitectureComparator>(std::move(result)); 121 } 122 else 123 { 124 return std::make_unique<MachineArchitectureComparator>(); 125 } 126 } 127 128 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 129 { 130 if (CheckAllowedArchitecture(installer.Arch) == Utility::InapplicableArchitecture || 131 IsSystemArchitectureUnsupportedByInstaller(installer)) 132 { 133 return InapplicabilityFlags::MachineArchitecture; 134 } 135 136 return InapplicabilityFlags::None; 137 } 138 139 std::string ExplainInapplicable(const ManifestInstaller& installer) override 140 { 141 std::string result; 142 if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) 143 { 144 result = "Machine is not compatible with "; 145 result += Utility::ToString(installer.Arch); 146 } 147 else if (IsSystemArchitectureUnsupportedByInstaller(installer)) 148 { 149 result = "System architecture is unsupported by installer"; 150 } 151 else 152 { 153 result = "Architecture was excluded by caller : "; 154 result += Utility::ToString(installer.Arch); 155 } 156 157 return result; 158 } 159 160 details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override 161 { 162 auto arch1 = CheckAllowedArchitecture(first.Arch); 163 auto arch2 = CheckAllowedArchitecture(second.Arch); 164 165 if (arch1 > arch2) 166 { 167 // A match with the primary architecture is strong 168 return (first.Arch == GetStrongArchitectureMatch() ? details::ComparisonResult::StrongPositive : details::ComparisonResult::WeakPositive); 169 } 170 171 return details::ComparisonResult::Negative; 172 } 173 174 private: 175 int CheckAllowedArchitecture(Utility::Architecture architecture) 176 { 177 if (m_allowedArchitectures.empty()) 178 { 179 return Utility::IsApplicableArchitecture(architecture); 180 } 181 else 182 { 183 return Utility::IsApplicableArchitecture(architecture, m_allowedArchitectures); 184 } 185 } 186 187 bool IsSystemArchitectureUnsupportedByInstaller(const ManifestInstaller& installer) 188 { 189 auto unsupportedItr = std::find( 190 installer.UnsupportedOSArchitectures.begin(), 191 installer.UnsupportedOSArchitectures.end(), 192 Utility::GetSystemArchitecture()); 193 return unsupportedItr != installer.UnsupportedOSArchitectures.end(); 194 } 195 196 Utility::Architecture GetStrongArchitectureMatch() 197 { 198 // If we have a preferential order, treat the first entry as strong. 199 // Otherwise, treat the system architecture as strong (which is always first in the default order). 200 return m_allowedArchitectures.empty() ? Utility::GetSystemArchitecture() : m_allowedArchitectures.front(); 201 } 202 203 std::vector<Utility::Architecture> m_allowedArchitectures; 204 }; 205 206 struct InstallerTypeComparator : public details::ComparisonField 207 { 208 InstallerTypeComparator(std::vector<InstallerTypeEnum> preference, std::vector<InstallerTypeEnum> requirement) : 209 details::ComparisonField("Installer Type"), m_preference(std::move(preference)), m_requirement(std::move(requirement)) 210 { 211 m_preferenceAsString = Utility::ConvertContainerToString(m_preference, InstallerTypeToString); 212 m_requirementAsString = Utility::ConvertContainerToString(m_requirement, InstallerTypeToString); 213 AICLI_LOG(CLI, Verbose, 214 << "InstallerType Comparator created with Required InstallerTypes: " << m_requirementAsString 215 << " , Preferred InstallerTypes: " << m_preferenceAsString); 216 } 217 218 static std::unique_ptr<InstallerTypeComparator> Create(const ManifestComparator::Options& options) 219 { 220 std::vector<InstallerTypeEnum> preference; 221 std::vector<InstallerTypeEnum> requirement; 222 223 if (options.RequestedInstallerType) 224 { 225 requirement.emplace_back(options.RequestedInstallerType.value()); 226 } 227 else 228 { 229 preference = Settings::User().Get<Settings::Setting::InstallerTypePreference>(); 230 requirement = Settings::User().Get<Settings::Setting::InstallerTypeRequirement>(); 231 } 232 233 if (!preference.empty() || !requirement.empty()) 234 { 235 return std::make_unique<InstallerTypeComparator>(preference, requirement); 236 } 237 else 238 { 239 return {}; 240 } 241 } 242 243 std::string ExplainInapplicable(const ManifestInstaller& installer) override 244 { 245 std::string result = "InstallerType ["; 246 result += InstallerTypeToString(installer.EffectiveInstallerType()); 247 result += "] does not match required InstallerTypes: "; 248 result += m_requirementAsString; 249 return result; 250 } 251 252 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 253 { 254 if (!m_requirement.empty()) 255 { 256 // The installer is applicable if the effective or base installer type matches. 257 if (ContainsInstallerType(m_requirement, installer.EffectiveInstallerType()) || 258 ContainsInstallerType(m_requirement, installer.BaseInstallerType)) 259 { 260 return InapplicabilityFlags::None; 261 } 262 263 return InapplicabilityFlags::InstallerType; 264 } 265 else 266 { 267 return InapplicabilityFlags::None; 268 } 269 } 270 271 details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override 272 { 273 if (m_preference.empty()) 274 { 275 return details::ComparisonResult::Negative; 276 } 277 278 for (InstallerTypeEnum installerTypePreference : m_preference) 279 { 280 bool isFirstInstallerTypePreferred = 281 first.EffectiveInstallerType() == installerTypePreference || 282 first.BaseInstallerType == installerTypePreference; 283 284 bool isSecondInstallerTypePreferred = 285 second.EffectiveInstallerType() == installerTypePreference || 286 second.BaseInstallerType == installerTypePreference; 287 288 if (isFirstInstallerTypePreferred && isSecondInstallerTypePreferred) 289 { 290 return details::ComparisonResult::Negative; 291 } 292 else if (isFirstInstallerTypePreferred != isSecondInstallerTypePreferred) 293 { 294 // Treating this as a weak positive because one can use requirements to guarantee the installer type if necessary. 295 return (isFirstInstallerTypePreferred ? details::ComparisonResult::WeakPositive : details::ComparisonResult::Negative); 296 } 297 } 298 299 return details::ComparisonResult::Negative; 300 } 301 302 private: 303 std::vector<InstallerTypeEnum> m_preference; 304 std::vector<InstallerTypeEnum> m_requirement; 305 std::string m_preferenceAsString; 306 std::string m_requirementAsString; 307 308 bool ContainsInstallerType(const std::vector<InstallerTypeEnum>& selection, InstallerTypeEnum installerType) 309 { 310 return std::find(selection.begin(), selection.end(), installerType) != selection.end(); 311 } 312 }; 313 314 struct InstalledTypeFilter : public details::FilterField 315 { 316 InstalledTypeFilter(InstallerTypeEnum installedType) : 317 details::FilterField("Installed Type"), m_installedType(installedType) {} 318 319 static std::unique_ptr<InstalledTypeFilter> Create(const ManifestComparator::Options& options) 320 { 321 if (options.CurrentlyInstalledType) 322 { 323 InstallerTypeEnum installedType = options.CurrentlyInstalledType.value(); 324 if (installedType != InstallerTypeEnum::Unknown) 325 { 326 return std::make_unique<InstalledTypeFilter>(installedType); 327 } 328 } 329 330 return {}; 331 } 332 333 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 334 { 335 return IsInstallerCompatibleWith(installer, m_installedType) ? InapplicabilityFlags::None : InapplicabilityFlags::InstalledType; 336 } 337 338 std::string ExplainInapplicable(const ManifestInstaller& installer) override 339 { 340 std::string result = "Installed package type '" + std::string{ InstallerTypeToString(m_installedType) } + 341 "' is not compatible with installer type " + std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }; 342 343 std::string arpInstallerTypes; 344 for (const auto& entry : installer.AppsAndFeaturesEntries) 345 { 346 arpInstallerTypes += " " + std::string{ InstallerTypeToString(entry.InstallerType) }; 347 } 348 349 if (!arpInstallerTypes.empty()) 350 { 351 result += ", or with accepted type(s)" + arpInstallerTypes; 352 } 353 354 return result; 355 } 356 357 private: 358 // The installer is compatible if it's type or any of its ARP entries' type matches the installed type 359 static bool IsInstallerCompatibleWith(const ManifestInstaller& installer, InstallerTypeEnum type) 360 { 361 if (IsInstallerTypeCompatible(installer.EffectiveInstallerType(), type)) 362 { 363 return true; 364 } 365 366 auto itr = std::find_if( 367 installer.AppsAndFeaturesEntries.begin(), 368 installer.AppsAndFeaturesEntries.end(), 369 [=](AppsAndFeaturesEntry arpEntry) { return IsInstallerTypeCompatible(arpEntry.InstallerType, type); }); 370 if (itr != installer.AppsAndFeaturesEntries.end()) 371 { 372 return true; 373 } 374 375 return false; 376 } 377 378 InstallerTypeEnum m_installedType; 379 }; 380 381 struct InstalledScopeFilter : public details::FilterField 382 { 383 InstalledScopeFilter(ScopeEnum requirement) : 384 details::FilterField("Installed Scope"), m_requirement(requirement) {} 385 386 static std::unique_ptr<InstalledScopeFilter> Create(const ManifestComparator::Options& options) 387 { 388 // Check for an existing install and require a matching scope. 389 if (options.CurrentlyInstalledScope) 390 { 391 ScopeEnum installedScope = options.CurrentlyInstalledScope.value(); 392 if (installedScope != ScopeEnum::Unknown) 393 { 394 return std::make_unique<InstalledScopeFilter>(installedScope); 395 } 396 } 397 398 return {}; 399 } 400 401 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 402 { 403 // We have to assume the unknown scope will match our required scope, or the entire catalog would stop working for upgrade. 404 if (installer.Scope == ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) 405 { 406 return InapplicabilityFlags::None; 407 } 408 409 return InapplicabilityFlags::InstalledScope; 410 } 411 412 std::string ExplainInapplicable(const ManifestInstaller& installer) override 413 { 414 std::string result = "Installer scope does not match currently installed scope: "; 415 result += ScopeToString(installer.Scope); 416 result += " != "; 417 result += ScopeToString(m_requirement); 418 return result; 419 } 420 421 private: 422 ScopeEnum m_requirement; 423 }; 424 425 struct ScopeComparator : public details::ComparisonField 426 { 427 ScopeComparator(ScopeEnum preference, ScopeEnum requirement, bool allowUnknownInAdditionToRequired) : 428 details::ComparisonField("Scope"), m_preference(preference), m_requirement(requirement), m_allowUnknownInAdditionToRequired(allowUnknownInAdditionToRequired) {} 429 430 static std::unique_ptr<ScopeComparator> Create(const ManifestComparator::Options& options) 431 { 432 // Preference will always come from settings 433 ScopeEnum preference = Settings::User().Get<Settings::Setting::InstallScopePreference>(); 434 435 // Requirement may come from args or settings; args overrides settings. 436 ScopeEnum requirement = ScopeEnum::Unknown; 437 438 if (options.RequestedInstallerScope) 439 { 440 requirement = options.RequestedInstallerScope.value(); 441 } 442 else 443 { 444 requirement = Settings::User().Get<Settings::Setting::InstallScopeRequirement>(); 445 } 446 447 bool allowUnknownInAdditionToRequired = false; 448 if (options.AllowUnknownScope) 449 { 450 allowUnknownInAdditionToRequired = options.AllowUnknownScope.value(); 451 452 // Force the required type to be preferred over Unknown 453 if (requirement != ScopeEnum::Unknown) 454 { 455 preference = requirement; 456 } 457 } 458 459 if (preference != ScopeEnum::Unknown || requirement != ScopeEnum::Unknown) 460 { 461 return std::make_unique<ScopeComparator>(preference, requirement, allowUnknownInAdditionToRequired); 462 } 463 else 464 { 465 return {}; 466 } 467 } 468 469 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 470 { 471 // Applicable if one of: 472 // 1. No requirement (aka is Unknown) 473 // 2. Requirement met 474 // 3. Installer scope is Unknown and this has been explicitly allowed 475 // 4. The installer type is scope agnostic (we can control it) 476 if (m_requirement == ScopeEnum::Unknown || 477 installer.Scope == m_requirement || 478 (installer.Scope == ScopeEnum::Unknown && m_allowUnknownInAdditionToRequired) || 479 DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) 480 { 481 return InapplicabilityFlags::None; 482 } 483 484 return InapplicabilityFlags::Scope; 485 } 486 487 std::string ExplainInapplicable(const ManifestInstaller& installer) override 488 { 489 std::string result = "Installer scope does not match required scope: "; 490 result += ScopeToString(installer.Scope); 491 result += " != "; 492 result += ScopeToString(m_requirement); 493 return result; 494 } 495 496 details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override 497 { 498 if (m_preference != ScopeEnum::Unknown && first.Scope == m_preference && second.Scope != m_preference) 499 { 500 // When the second input is unknown, this is a weak result. If it is not (and therefore the opposite of the preference), this is strong. 501 return (second.Scope == ScopeEnum::Unknown ? details::ComparisonResult::WeakPositive : details::ComparisonResult::StrongPositive); 502 } 503 504 return details::ComparisonResult::Negative; 505 } 506 507 private: 508 ScopeEnum m_preference; 509 ScopeEnum m_requirement; 510 bool m_allowUnknownInAdditionToRequired; 511 }; 512 513 struct LocaleComparator : public details::ComparisonField 514 { 515 LocaleComparator(std::vector<std::string> preference, std::vector<std::string> requirement, bool isInstalledLocale) : 516 details::ComparisonField("Locale"), m_preference(std::move(preference)), m_requirement(std::move(requirement)), m_isInstalledLocale(isInstalledLocale) 517 { 518 m_requirementAsString = Utility::ConvertContainerToString(m_requirement); 519 m_preferenceAsString = Utility::ConvertContainerToString(m_preference); 520 AICLI_LOG(CLI, Verbose, 521 << "Locale Comparator created with Required Locales: " << m_requirementAsString 522 << " , Preferred Locales: " << m_preferenceAsString 523 << " , IsInstalledLocale: " << m_isInstalledLocale); 524 } 525 526 static std::unique_ptr<LocaleComparator> Create(const ManifestComparator::Options& options) 527 { 528 std::vector<std::string> preference; 529 std::vector<std::string> requirement; 530 // This is for installed locale case, where the locale is a preference but requires at least compatible match. 531 bool isInstalledLocale = false; 532 533 // Requirement may come from args, previous user intent or settings; args overrides previous user intent then settings. 534 if (options.RequestedInstallerLocale) 535 { 536 requirement.emplace_back(options.RequestedInstallerLocale.value()); 537 } 538 else if (options.PreviousUserIntentLocale) 539 { 540 requirement.emplace_back(options.PreviousUserIntentLocale.value()); 541 isInstalledLocale = true; 542 } 543 else 544 { 545 if (!options.CurrentlyInstalledLocale) 546 { 547 // If it's an upgrade of previous package, no need to set requirements from settings 548 // as previous installed locale will be used later. 549 requirement = Settings::User().Get<Settings::Setting::InstallLocaleRequirement>(); 550 } 551 } 552 553 // Preference will come from previous installed locale, winget settings or Preferred Languages settings. 554 // Previous installed locale goes first, then winget settings, then Preferred Languages settings. 555 // Previous installed locale also requires at least compatible locale match. 556 if (options.CurrentlyInstalledLocale) 557 { 558 preference.emplace_back(options.CurrentlyInstalledLocale.value()); 559 isInstalledLocale = true; 560 } 561 else 562 { 563 preference = Settings::User().Get<Settings::Setting::InstallLocalePreference>(); 564 if (preference.empty()) 565 { 566 preference = Locale::GetUserPreferredLanguages(); 567 } 568 } 569 570 if (!preference.empty() || !requirement.empty()) 571 { 572 return std::make_unique<LocaleComparator>(preference, requirement, isInstalledLocale); 573 } 574 else 575 { 576 return {}; 577 } 578 } 579 580 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 581 { 582 InapplicabilityFlags inapplicableFlag = m_isInstalledLocale ? InapplicabilityFlags::InstalledLocale : InapplicabilityFlags::Locale; 583 584 if (!m_requirement.empty()) 585 { 586 // Check if requirement is satisfied 587 for (auto const& requiredLocale : m_requirement) 588 { 589 if (Locale::GetDistanceOfLanguage(requiredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsPerfectMatch) 590 { 591 return InapplicabilityFlags::None; 592 } 593 } 594 595 return inapplicableFlag; 596 } 597 else if (m_isInstalledLocale && !m_preference.empty()) 598 { 599 // For installed locale preference, check at least compatible match for preference 600 for (auto const& preferredLocale : m_preference) 601 { 602 // We have to assume an unknown installer locale will match our installed locale, or the entire catalog would stop working for upgrade. 603 if (installer.Locale.empty() || 604 Locale::GetDistanceOfLanguage(preferredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch) 605 { 606 return InapplicabilityFlags::None; 607 } 608 } 609 610 return inapplicableFlag; 611 } 612 else 613 { 614 return InapplicabilityFlags::None; 615 } 616 } 617 618 std::string ExplainInapplicable(const ManifestInstaller& installer) override 619 { 620 std::string result = "Installer locale does not match required locale: "; 621 result += installer.Locale; 622 result += "Required locales: "; 623 result += m_requirementAsString; 624 result += " Or does not satisfy compatible match for Preferred Locales: "; 625 result += m_preferenceAsString; 626 return result; 627 } 628 629 details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override 630 { 631 if (m_preference.empty()) 632 { 633 return details::ComparisonResult::Negative; 634 } 635 636 for (auto const& preferredLocale : m_preference) 637 { 638 double firstScore = first.Locale.empty() ? Locale::UnknownLanguageDistanceScore : Locale::GetDistanceOfLanguage(preferredLocale, first.Locale); 639 double secondScore = second.Locale.empty() ? Locale::UnknownLanguageDistanceScore : Locale::GetDistanceOfLanguage(preferredLocale, second.Locale); 640 641 if (firstScore >= Locale::MinimumDistanceScoreAsCompatibleMatch || secondScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) 642 { 643 // This could probably be enriched to always check all locales and determine strong/weak based off of the MinimumDistanceScoreAsCompatibleMatch. 644 return (firstScore > secondScore ? details::ComparisonResult::StrongPositive : details::ComparisonResult::Negative); 645 } 646 } 647 648 // At this point, the installer locale matches no preference. 649 // if first is unknown and second is no match for sure, we might prefer unknown one. 650 return (first.Locale.empty() && !second.Locale.empty() ? details::ComparisonResult::WeakPositive : details::ComparisonResult::Negative); 651 } 652 653 private: 654 std::vector<std::string> m_preference; 655 std::vector<std::string> m_requirement; 656 std::string m_requirementAsString; 657 std::string m_preferenceAsString; 658 bool m_isInstalledLocale = false; 659 }; 660 661 struct MarketFilter : public details::FilterField 662 { 663 MarketFilter(Manifest::string_t market) : details::FilterField("Market"), m_market(market) 664 { 665 AICLI_LOG(CLI, Verbose, << "Market Filter created with market: " << m_market); 666 } 667 668 static std::unique_ptr<MarketFilter> Create() 669 { 670 return std::make_unique<MarketFilter>(Runtime::GetOSRegion()); 671 } 672 673 InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override 674 { 675 // If both allowed and excluded lists are provided, we only need to check the allowed markets. 676 if (!installer.Markets.AllowedMarkets.empty()) 677 { 678 // Inapplicable if NOT found 679 if (!IsMarketInList(installer.Markets.AllowedMarkets)) 680 { 681 return InapplicabilityFlags::Market; 682 } 683 } 684 else if (!installer.Markets.ExcludedMarkets.empty()) 685 { 686 // Inapplicable if found 687 if (IsMarketInList(installer.Markets.ExcludedMarkets)) 688 { 689 return InapplicabilityFlags::Market; 690 } 691 } 692 693 return InapplicabilityFlags::None; 694 } 695 696 std::string ExplainInapplicable(const ManifestInstaller& installer) override 697 { 698 std::string result = "Current market '" + m_market + "' does not match installer markets." + 699 " Allowed markets: " + Utility::ConvertContainerToString(installer.Markets.AllowedMarkets) + 700 " Excluded markets: " + Utility::ConvertContainerToString(installer.Markets.ExcludedMarkets); 701 return result; 702 } 703 704 private: 705 bool IsMarketInList(const std::vector<Manifest::string_t> markets) 706 { 707 return markets.end() != std::find_if( 708 markets.begin(), 709 markets.end(), 710 [&](const auto& m) { return Utility::CaseInsensitiveEquals(m, m_market); }); 711 } 712 713 Manifest::string_t m_market; 714 }; 715 } 716 717 ManifestComparator::ManifestComparator(const Options& options) 718 { 719 // Filters based on installer's MinOSVersion 720 AddFilter(std::make_unique<OSVersionFilter>()); 721 // Filters out portable installers if they are not supported by the system 722 AddFilter(std::make_unique<PortableInstallFilter>()); 723 // Filters based on the scope of a currently installed package 724 AddFilter(InstalledScopeFilter::Create(options)); 725 // Filters based on the market region of the system 726 AddFilter(MarketFilter::Create()); 727 // Filters based on the installer type compatability, including with AppsAndFeaturesEntry declarations 728 AddFilter(InstalledTypeFilter::Create(options)); 729 730 // Filter order is not important, but comparison order determines priority. 731 // Note that all comparators are also filters and their comparison function will only be called on 732 // installers that both match the required criteria. 733 // 734 // The comparators are ordered by the `IsFirstBetter` method, which uses the following algorithm: 735 // - Each comparison between two installers can return one of { Strong, Weak, Negative } 736 // - Installers are compared in both directions, going through the list of comparators as defined here 737 // - The first Strong result in either direction is given priority 738 // - If no Strong results, the first Weak result is used 739 // - If all Negative results, then the two installers are equal in priority (meaning the first one in the list is kept as "better") 740 // 741 // TODO: There are improvements to be made here around ordering, especially in the context of implicit vs explicit vs command line preferences. 742 743 // Filters based on exact matches for requirements or compatible matches for preferences 744 // Only applies when preference exists: 745 // Strong if first is compatible and better match than second 746 // Weak if first is unknown and second is not 747 AddComparator(LocaleComparator::Create(options)); 748 // Filters only if a requirement is present and it cannot be satisfied by the installer (including installer types that we can control scope in code) 749 // Only applies when preference exists: 750 // Strong if first matches preference and second does not and is not Unknown 751 // Weak if first matches preference and second is Unknown 752 AddComparator(ScopeComparator::Create(options)); 753 // Filters architectures out that are not supported or are not in the preferences/requirements/inputs. 754 // Strong if first equals the earliest architecture in the allowed list and second does not [default means the system architecture] 755 // Weak if first is better match for system architecture than second 756 AddComparator(MachineArchitectureComparator::Create(options)); 757 // Filters installer types out that are not in preferences or requirements. 758 // Only applies when preference exists: 759 // Weak if first is in preference list and second is not 760 AddComparator(InstallerTypeComparator::Create(options)); 761 } 762 763 InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest& manifest) 764 { 765 AICLI_LOG(CLI, Verbose, << "Starting installer selection."); 766 767 const ManifestInstaller* result = nullptr; 768 std::vector<InapplicabilityFlags> inapplicabilitiesInstallers; 769 770 for (const auto& installer : manifest.Installers) 771 { 772 auto inapplicabilityInstaller = IsApplicable(installer); 773 if (inapplicabilityInstaller == InapplicabilityFlags::None) 774 { 775 if (!result || IsFirstBetter(installer, *result)) 776 { 777 AICLI_LOG(CLI, Verbose, << "Installer " << installer << " is current best choice"); 778 result = &installer; 779 } 780 } 781 else 782 { 783 inapplicabilitiesInstallers.push_back(inapplicabilityInstaller); 784 } 785 } 786 787 if (!result) 788 { 789 return { {}, std::move(inapplicabilitiesInstallers) }; 790 } 791 792 return { *result, std::move(inapplicabilitiesInstallers) }; 793 } 794 795 InapplicabilityFlags ManifestComparator::IsApplicable(const ManifestInstaller& installer) 796 { 797 InapplicabilityFlags inapplicabilityResult = InapplicabilityFlags::None; 798 799 for (const auto& filter : m_filters) 800 { 801 auto inapplicability = filter->IsApplicable(installer); 802 if (inapplicability != InapplicabilityFlags::None) 803 { 804 AICLI_LOG(CLI, Verbose, << "Installer " << installer << " not applicable: " << filter->ExplainInapplicable(installer)); 805 WI_SetAllFlags(inapplicabilityResult, inapplicability); 806 } 807 } 808 809 return inapplicabilityResult; 810 } 811 812 bool ManifestComparator::IsFirstBetter( 813 const ManifestInstaller& first, 814 const ManifestInstaller& second) 815 { 816 // The priority will still be used as a tie-break between weak results. 817 std::optional<std::string_view> firstWeakComparator; 818 bool firstWeakComparatorResult = false; 819 820 for (auto comparator : m_comparators) 821 { 822 details::ComparisonResult forwardCompare = comparator->IsFirstBetter(first, second); 823 details::ComparisonResult reverseCompare = comparator->IsFirstBetter(second, first); 824 825 // Should not happen, but if it does it points at a serious bug that should be fixed. 826 if (forwardCompare != details::ComparisonResult::Negative && reverseCompare != details::ComparisonResult::Negative) 827 { 828 AICLI_LOG(CLI, Error, << "Installer " << first << " and " << second << " are both better than each other?"); 829 THROW_HR(E_UNEXPECTED); 830 } 831 832 if (forwardCompare == details::ComparisonResult::StrongPositive) 833 { 834 AICLI_LOG(CLI, Verbose, << "Installer " << first << " is better [strong] than " << second << " due to: " << comparator->Name()); 835 return true; 836 } 837 838 if (reverseCompare == details::ComparisonResult::StrongPositive) 839 { 840 // Second is better by this comparator, don't allow a lower priority one to override that. 841 AICLI_LOG(CLI, Verbose, << "Installer " << second << " is better [strong] than " << first << " due to: " << comparator->Name()); 842 return false; 843 } 844 845 // Save the first weak result that we get 846 if (!firstWeakComparator) 847 { 848 if (forwardCompare == details::ComparisonResult::WeakPositive) 849 { 850 firstWeakComparator = comparator->Name(); 851 firstWeakComparatorResult = true; 852 } 853 else if (reverseCompare == details::ComparisonResult::WeakPositive) 854 { 855 firstWeakComparator = comparator->Name(); 856 firstWeakComparatorResult = false; 857 } 858 } 859 } 860 861 // If we found a weak result (and no strong result because we made it here), return it. 862 if (firstWeakComparator) 863 { 864 if (firstWeakComparatorResult) 865 { 866 AICLI_LOG(CLI, Verbose, << "Installer " << first << " is better [weak] than " << second << " due to: " << *firstWeakComparator); 867 } 868 else 869 { 870 AICLI_LOG(CLI, Verbose, << "Installer " << second << " is better [weak] than " << first << " due to: " << *firstWeakComparator); 871 } 872 873 return firstWeakComparatorResult; 874 } 875 876 // Equal, and thus not better 877 AICLI_LOG(CLI, Verbose, << "Installer " << first << " and " << second << " are equivalent in priority"); 878 return false; 879 } 880 881 void ManifestComparator::AddFilter(std::unique_ptr<details::FilterField>&& filter) 882 { 883 if (filter) 884 { 885 m_filters.emplace_back(std::move(filter)); 886 } 887 } 888 889 void ManifestComparator::AddComparator(std::unique_ptr<details::ComparisonField>&& comparator) 890 { 891 if (comparator) 892 { 893 m_comparators.push_back(comparator.get()); 894 m_filters.emplace_back(std::move(comparator)); 895 } 896 } 897 }