winget-cli

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

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 }