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 }