winget-cli

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

PackageDependenciesValidation.cpp (8989B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #pragma once
      4 #include "pch.h"
      5 #include "PackageDependenciesValidation.h"
      6 #include <winget/ManifestValidation.h>
      7 #include <winget/DependenciesGraph.h>
      8 
      9 namespace AppInstaller::Repository
     10 {
     11     namespace
     12     {
     13         struct DependentManifestInfo
     14         {
     15             Utility::NormalizedString Id;
     16             Utility::NormalizedString Version;
     17         };
     18 
     19         Manifest::DependencyList GetDependencies(
     20             const Manifest::Manifest& manifest, AppInstaller::Manifest::DependencyType dependencyType)
     21         {
     22             Manifest::DependencyList depList;
     23             std::vector<AppInstaller::Manifest::Dependency>  dependencies;
     24 
     25             for (const auto& installer : manifest.Installers)
     26             {
     27                 installer.Dependencies.ApplyToType(dependencyType, [&](AppInstaller::Manifest::Dependency dependency)
     28                 {
     29                     depList.Add(dependency);
     30                 });
     31             }
     32 
     33             return depList;
     34         }
     35 
     36         std::optional<SQLiteIndex::VersionKey> GetPackageLatestVersion(
     37             SQLiteIndex* index, Manifest::string_t packageId, std::set<Utility::Version> exclusions = {})
     38         {
     39             SearchRequest request;
     40             request.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageId);
     41 
     42             auto results = index->Search(request);
     43 
     44             if (results.Matches.empty())
     45             {
     46                 return {};
     47             }
     48 
     49             auto packageRowId = results.Matches[0].first;
     50             auto vac = index->GetVersionKeysById(packageRowId);
     51 
     52             if (vac.empty())
     53             {
     54                 return {};
     55             }
     56 
     57             SQLiteIndex::VersionKey maxVersion{ Utility::VersionAndChannel{ Utility::Version::CreateUnknown(), Utility::Channel("") } };
     58 
     59             for (auto& v : vac)
     60             {
     61                 auto currentVersion = v.VersionAndChannel.GetVersion();
     62                 if (exclusions.find(currentVersion) != exclusions.end())
     63                 {
     64                     continue;
     65                 }
     66 
     67                 if (currentVersion > maxVersion.VersionAndChannel.GetVersion())
     68                 {
     69                     maxVersion = v;
     70                 }
     71             }
     72 
     73             if (maxVersion.VersionAndChannel.GetVersion().IsUnknown())
     74             {
     75                 return {};
     76             }
     77 
     78             return maxVersion;
     79         }
     80     
     81         void ThrowOnManifestValidationFailed(
     82             std::vector<std::pair<DependentManifestInfo, Utility::Version>> failedManifests, AppInstaller::StringResource::StringId error)
     83         {
     84             auto itrStart = failedManifests.begin();
     85             std::vector<Manifest::ValidationError> validationErrors;
     86             validationErrors.emplace_back(error, "PackageIdentifier.PackageVersion", itrStart->first.Id + '.' + itrStart->first.Version );
     87 
     88             std::for_each(
     89                 itrStart + 1,
     90                 failedManifests.end(),
     91                 [&](std::pair<DependentManifestInfo, Utility::Version> current)
     92                 {
     93                     validationErrors.emplace_back(error, "PackageIdentifier.PackageVersion", current.first.Id + '.' + current.first.Version);
     94                 });
     95 
     96             THROW_EXCEPTION(
     97                 Manifest::ManifestException(
     98                     std::move(validationErrors), APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED));
     99         }
    100     };
    101 
    102     bool PackageDependenciesValidation::ValidateManifestDependencies(SQLiteIndex* index, const Manifest::Manifest& manifest)
    103     {
    104         using namespace Manifest;
    105 
    106         Dependency rootId(DependencyType::Package, manifest.Id, manifest.Version);
    107         std::vector<ValidationError> dependenciesError;
    108         bool foundErrors = false;
    109 
    110         DependencyGraph graph
    111         {
    112             rootId,
    113             [&](const Dependency& node)
    114             {
    115                 DependencyList depList;
    116                 if (node.Id() == rootId.Id())
    117                 {
    118                     return GetDependencies(manifest, DependencyType::Package);
    119                 }
    120 
    121                 auto packageLatest = GetPackageLatestVersion(index, node.Id());
    122                 if (!packageLatest.has_value())
    123                 {
    124                     dependenciesError.emplace_back(
    125                         ManifestError::MissingManifestDependenciesNode, "PackageIdentifier", node.Id());
    126                     foundErrors = true;
    127                     return depList;
    128                 }
    129 
    130                 if (node.MinVersion > packageLatest->VersionAndChannel.GetVersion())
    131                 {
    132                     dependenciesError.emplace_back(ManifestError::NoSuitableMinVersionDependency, "PackageIdentifier", node.Id());
    133                     foundErrors = true;
    134                     return depList;
    135                 }
    136 
    137                 auto packageLatestDependencies = index->GetDependenciesByManifestRowId(packageLatest->ManifestId);
    138                 std::for_each(
    139                     packageLatestDependencies.begin(),
    140                     packageLatestDependencies.end(),
    141                     [&](std::pair<SQLite::rowid_t, Utility::NormalizedString> row)
    142                     {
    143                         auto manifestRowId = index->GetManifestIdByKey(row.first, "", "");
    144                         auto packageId = index->GetPropertyByPrimaryId(manifestRowId.value(), PackageVersionProperty::Id);
    145                         Dependency dep(DependencyType::Package, packageId.value(), row.second);
    146                         depList.Add(dep);
    147                     });
    148 
    149                 return depList;
    150             }
    151         };
    152 
    153         graph.BuildGraph();
    154 
    155         if (foundErrors)
    156         {
    157             THROW_EXCEPTION(ManifestException(std::move(dependenciesError), APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED));
    158         }
    159 
    160         if (graph.HasLoop())
    161         {
    162             dependenciesError.emplace_back(ManifestError::FoundDependencyLoop);
    163             THROW_EXCEPTION(ManifestException(std::move(dependenciesError), APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED));
    164         }
    165 
    166         return true;
    167     }
    168 
    169     bool PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(SQLiteIndex* index, const Manifest::Manifest& manifest)
    170     {
    171         auto dependentsSet = index->GetDependentsById(manifest.Id);
    172 
    173         if (!dependentsSet.size())
    174         {
    175             // all good this manifest is not a dependency of any manifest.
    176             return true;
    177         }
    178 
    179         std::vector<std::pair<DependentManifestInfo, Utility::Version>> dependentManifestInfoToVersionPair;
    180         std::for_each(
    181             dependentsSet.begin(),
    182             dependentsSet.end(),
    183             [&](std::pair<SQLite::rowid_t, Utility::Version> current)
    184             {
    185                 DependentManifestInfo dependentManifestInfo;
    186                 dependentManifestInfo.Id = index->GetPropertyByPrimaryId(current.first, PackageVersionProperty::Id).value();
    187                 dependentManifestInfo.Version = index->GetPropertyByPrimaryId(current.first, PackageVersionProperty::Version).value();
    188 
    189                 dependentManifestInfoToVersionPair.emplace_back(std::make_pair(dependentManifestInfo, current.second));
    190             });
    191 
    192         auto packageLatest = GetPackageLatestVersion(index, manifest.Id);
    193 
    194         if (!packageLatest.has_value())
    195         {
    196             // this is a fatal error, a manifest should exists in the very least(including the current manifest being deleted),
    197             // since this is a delete operation. 
    198             THROW_HR(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE);
    199         }
    200 
    201         if (Utility::Version(manifest.Version) < packageLatest->VersionAndChannel.GetVersion())
    202         {
    203             // all good, since it's min version the criteria is still satisfied.
    204             return true;
    205         }
    206 
    207         auto nextLatestAfterDelete = GetPackageLatestVersion(index, manifest.Id, { packageLatest->VersionAndChannel.GetVersion() });
    208 
    209         if (!nextLatestAfterDelete.has_value())
    210         {
    211             ThrowOnManifestValidationFailed(
    212                 dependentManifestInfoToVersionPair, Manifest::ManifestError::SingleManifestPackageHasDependencies);
    213         }
    214 
    215         std::vector<std::pair<DependentManifestInfo, Utility::Version>> breakingManifests;
    216 
    217         // Gets breaking manifests.
    218         std::copy_if(
    219             dependentManifestInfoToVersionPair.begin(),
    220             dependentManifestInfoToVersionPair.end(),
    221             std::back_inserter(breakingManifests),
    222             [&](std::pair<DependentManifestInfo, Utility::Version> current)
    223             {
    224                 return current.second > nextLatestAfterDelete->VersionAndChannel.GetVersion();
    225             }
    226         );
    227 
    228         if (!breakingManifests.empty())
    229         {
    230             ThrowOnManifestValidationFailed(
    231                 breakingManifests, Manifest::ManifestError::MultiManifestPackageHasDependencies);
    232         }
    233 
    234         return true;
    235     }
    236 }