DependenciesFlow.cpp (15063B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 4 #include "pch.h" 5 #include "DependenciesFlow.h" 6 #include <winget/ManifestComparator.h> 7 #include "InstallFlow.h" 8 #include "winget\DependenciesGraph.h" 9 #include "DependencyNodeProcessor.h" 10 #include "ShellExecuteInstallerHandler.h" 11 12 using namespace AppInstaller::Repository; 13 using namespace AppInstaller::Manifest; 14 15 namespace AppInstaller::CLI::Workflow 16 { 17 namespace 18 { 19 // Contains all the information needed to install a dependency package. 20 struct DependencyPackageCandidate 21 { 22 DependencyPackageCandidate( 23 std::shared_ptr<Repository::IPackageVersion>&& packageVersion, 24 std::shared_ptr<Repository::IPackageVersion>&& installedPackageVersion, 25 Manifest::Manifest&& manifest, 26 Manifest::ManifestInstaller&& installer) 27 : PackageVersion(std::move(packageVersion)), InstalledPackageVersion(std::move(installedPackageVersion)), Manifest(std::move(manifest)), Installer(std::move(installer)) { } 28 29 std::shared_ptr<Repository::IPackageVersion> PackageVersion; 30 std::shared_ptr<Repository::IPackageVersion> InstalledPackageVersion; 31 Manifest::Manifest Manifest; 32 Manifest::ManifestInstaller Installer; 33 }; 34 } 35 36 void ReportDependencies::operator()(Execution::Context& context) const 37 { 38 auto info = context.Reporter.Info(); 39 40 const auto& dependencies = context.Get<Execution::Data::Dependencies>(); 41 if (dependencies.HasAny()) 42 { 43 info << m_messageId << std::endl; 44 45 if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) 46 { 47 info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; 48 dependencies.ApplyToType(DependencyType::WindowsFeature, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); 49 } 50 51 if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) 52 { 53 info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; 54 dependencies.ApplyToType(DependencyType::WindowsLibrary, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); 55 } 56 57 if (dependencies.HasAnyOf(DependencyType::Package)) 58 { 59 info << " - " << Resource::String::PackageDependencies << std::endl; 60 dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) 61 { 62 info << " " << dependency.Id(); 63 if (dependency.MinVersion) 64 { 65 info << " [>= " << dependency.MinVersion.value().ToString() << "]"; 66 } 67 info << std::endl; 68 }); 69 } 70 71 if (dependencies.HasAnyOf(DependencyType::External)) 72 { 73 context.Reporter.Warn() << " - " << Resource::String::ExternalDependencies << std::endl; 74 dependencies.ApplyToType(DependencyType::External, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); 75 } 76 } 77 } 78 79 void GetInstallersDependenciesFromManifest(Execution::Context& context) 80 { 81 const auto& manifest = context.Get<Execution::Data::Manifest>(); 82 DependencyList allDependencies; 83 84 for (const auto& installer : manifest.Installers) 85 { 86 allDependencies.Add(installer.Dependencies); 87 } 88 89 context.Add<Execution::Data::Dependencies>(std::move(allDependencies)); 90 } 91 92 void GetDependenciesFromInstaller(Execution::Context& context) 93 { 94 const auto& installer = context.Get<Execution::Data::Installer>(); 95 if (installer) 96 { 97 context.Add<Execution::Data::Dependencies>(installer->Dependencies); 98 } 99 } 100 101 void GetDependenciesInfoForUninstall(Execution::Context& context) 102 { 103 // TODO make best effort to get the correct installer information, it may be better to have a record of installations and save the correct installers 104 context.Add<Execution::Data::Dependencies>(DependencyList()); // sending empty list of dependencies for now 105 } 106 107 void OpenDependencySource(Execution::Context& context) 108 { 109 if (context.Contains(Execution::Data::PackageVersion)) 110 { 111 const auto& packageVersion = context.Get<Execution::Data::PackageVersion>(); 112 context.Add<Execution::Data::DependencySource>(packageVersion->GetSource()); 113 } 114 else 115 { 116 // install from manifest requires --dependency-source to be set 117 context << 118 Workflow::OpenSource(true); 119 } 120 121 if (WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) 122 { 123 // Installed source is not needed when only downloading the installer. 124 context << 125 Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true, Repository::CompositeSearchBehavior::AvailablePackages); 126 } 127 } 128 129 void EnableWindowsFeaturesDependencies(Execution::Context& context) 130 { 131 const auto& rootDependencies = context.Get<Execution::Data::Installer>()->Dependencies; 132 133 if (rootDependencies.Empty() || !rootDependencies.HasAnyOf(DependencyType::WindowsFeature)) 134 { 135 return; 136 } 137 138 context << Workflow::EnsureRunningAsAdmin; 139 140 if (context.IsTerminated()) 141 { 142 return; 143 } 144 145 bool isCancelled = false; 146 bool enableFeaturesFailed = false; 147 bool rebootRequired = false; 148 bool force = context.Args.Contains(Execution::Args::Type::Force); 149 150 rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&context, &isCancelled, &enableFeaturesFailed, &force, &rebootRequired](Dependency dependency) 151 { 152 if (enableFeaturesFailed && !force || isCancelled) 153 { 154 return; 155 } 156 157 auto featureName = dependency.Id(); 158 159 auto featureContextPtr = context.CreateSubContext(); 160 Execution::Context& featureContext = *featureContextPtr; 161 auto previousThreadGlobals = featureContext.SetForCurrentThread(); 162 163 featureContext << Workflow::ShellExecuteEnableWindowsFeature(featureName); 164 165 if (featureContext.IsTerminated()) 166 { 167 isCancelled = true; 168 return; 169 } 170 171 Utility::LocIndView locIndFeatureName{ featureName }; 172 DWORD result = featureContext.Get<Execution::Data::OperationReturnCode>(); 173 174 if (result == ERROR_SUCCESS) 175 { 176 AICLI_LOG(Core, Info, << "Successfully enabled [" << featureName << "]"); 177 } 178 else if (result == 0x800f080c) // DISMAPI_E_UNKNOWN_FEATURE 179 { 180 AICLI_LOG(Core, Warning, << "Windows Feature [" << featureName << "] does not exist"); 181 enableFeaturesFailed = true; 182 featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl; 183 } 184 else if (result == ERROR_SUCCESS_REBOOT_REQUIRED) 185 { 186 AICLI_LOG(Core, Info, << "Reboot required for [" << featureName << "]"); 187 rebootRequired = true; 188 } 189 else 190 { 191 AICLI_LOG(Core, Error, << "Failed to enable Windows Feature [" << featureName << "] with exit code: " << result); 192 enableFeaturesFailed = true; 193 featureContext.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeature(locIndFeatureName, result) << std::endl; 194 } 195 }); 196 197 if (isCancelled) 198 { 199 context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; 200 AICLI_TERMINATE_CONTEXT(E_ABORT); 201 } 202 203 if (enableFeaturesFailed) 204 { 205 if (force) 206 { 207 context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeatureOverridden << std::endl; 208 } 209 else 210 { 211 context.Reporter.Error() << Resource::String::FailedToEnableWindowsFeatureOverrideRequired << std::endl; 212 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); 213 } 214 } 215 else 216 { 217 if (rebootRequired) 218 { 219 if (force) 220 { 221 context.Reporter.Warn() << Resource::String::RebootRequiredToEnableWindowsFeatureOverridden << std::endl; 222 } 223 else 224 { 225 context.Reporter.Error() << Resource::String::RebootRequiredToEnableWindowsFeatureOverrideRequired << std::endl; 226 context.SetFlags(Execution::ContextFlag::RegisterResume); 227 context.SetFlags(Execution::ContextFlag::RebootRequired); 228 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL); 229 } 230 } 231 else 232 { 233 context.Reporter.Info() << Resource::String::EnableWindowsFeaturesSuccess << std::endl; 234 } 235 } 236 } 237 238 void CreateDependencySubContexts::operator()(Execution::Context& context) const 239 { 240 if (Settings::User().Get<Settings::Setting::InstallSkipDependencies>() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) 241 { 242 return; 243 } 244 245 auto info = context.Reporter.Info(); 246 auto error = context.Reporter.Error(); 247 const auto& rootManifest = context.Get<Execution::Data::Manifest>(); 248 249 Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); 250 251 const auto& rootInstaller = context.Get<Execution::Data::Installer>(); 252 const auto& rootDependencies = rootInstaller->Dependencies; 253 254 if (rootDependencies.Empty()) 255 { 256 // If there's no dependencies there's nothing to do aside of logging the outcome 257 return; 258 } 259 260 context << OpenDependencySource; 261 if (context.IsTerminated()) 262 { 263 info << Resource::String::DependenciesFlowSourceNotFound << std::endl; 264 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); 265 } 266 267 std::map<string_t, DependencyPackageCandidate> idToPackageMap; 268 bool foundError = false; 269 DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, 270 [&](Dependency node) 271 { 272 DependencyNodeProcessor nodeProcessor(context); 273 274 auto result = nodeProcessor.EvaluateDependencies(node); 275 DependencyList list = nodeProcessor.GetDependencyList(); 276 foundError = foundError || (result == DependencyNodeProcessorResult::Error); 277 278 if (result == DependencyNodeProcessorResult::Success) 279 { 280 DependencyPackageCandidate dependencyPackageCandidate{ 281 std::move(nodeProcessor.GetPackageLatestVersion()), 282 std::move(nodeProcessor.GetPackageInstalledVersion()), 283 std::move(nodeProcessor.GetManifest()), 284 std::move(nodeProcessor.GetPreferredInstaller()) }; 285 286 idToPackageMap.emplace(node.Id(), std::move(dependencyPackageCandidate)); 287 }; 288 289 return list; 290 }); 291 292 dependencyGraph.BuildGraph(); 293 294 if (foundError) 295 { 296 error << Resource::String::DependenciesManagementExitMessage << std::endl; 297 AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY); 298 } 299 300 if (dependencyGraph.HasLoop()) 301 { 302 context.Reporter.Warn() << Resource::String::DependenciesFlowContainsLoop; 303 } 304 305 const auto& installationOrder = dependencyGraph.GetInstallationOrder(); 306 307 std::vector<std::unique_ptr<Execution::Context>> dependencyPackageContexts; 308 309 for (auto const& node : installationOrder) 310 { 311 auto itr = idToPackageMap.find(node.Id()); 312 // if the package was already installed (with a useful version) or is the root 313 // then there will be no installer for it on the map. 314 if (itr != idToPackageMap.end()) 315 { 316 auto dependencyContextPtr = context.CreateSubContext(); 317 Execution::Context& dependencyContext = *dependencyContextPtr; 318 auto previousThreadGlobals = dependencyContext.SetForCurrentThread(); 319 320 Logging::Telemetry().LogSelectedInstaller( 321 static_cast<int>(itr->second.Installer.Arch), 322 itr->second.Installer.Url, 323 Manifest::InstallerTypeToString(itr->second.Installer.EffectiveInstallerType()), 324 Manifest::ScopeToString(itr->second.Installer.Scope), 325 itr->second.Installer.Locale); 326 327 Logging::Telemetry().LogManifestFields( 328 itr->second.Manifest.Id, 329 itr->second.Manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>(), 330 itr->second.Manifest.Version); 331 332 // Extract the data needed for installing 333 dependencyContext.Add<Execution::Data::PackageVersion>(itr->second.PackageVersion); 334 dependencyContext.Add<Execution::Data::Manifest>(itr->second.Manifest); 335 dependencyContext.Add<Execution::Data::InstalledPackageVersion>(itr->second.InstalledPackageVersion); 336 dependencyContext.Add<Execution::Data::Installer>(itr->second.Installer); 337 338 if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) 339 { 340 dependencyContext.Add<Execution::Data::DownloadDirectory>(context.Get<Execution::Data::DownloadDirectory>() / L"Dependencies"); 341 } 342 343 dependencyPackageContexts.emplace_back(std::move(dependencyContextPtr)); 344 } 345 } 346 347 context.Add<Execution::Data::PackageSubContexts>(std::move(dependencyPackageContexts)); 348 } 349 }