winget-cli

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

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 }