From 021e326ea32e8b357749e60def46f7aab33a741a Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Tue, 29 Jun 2021 18:14:57 -0300 Subject: [PATCH 01/41] proto --- .../Workflows/DependenciesFlow.cpp | 81 ++++++++++++++++--- .../Workflows/DependenciesFlow.h | 6 ++ .../Workflows/InstallFlow.cpp | 10 +-- .../Workflows/InstallFlow.h | 9 +++ src/AppInstallerCLITests/WorkFlow.cpp | 33 ++++++++ 5 files changed, 120 insertions(+), 19 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 41bb4e9663..49a0931034 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -3,9 +3,14 @@ #include "pch.h" #include "DependenciesFlow.h" +#include "ManifestComparator.h" +#include "InstallFlow.h" namespace AppInstaller::CLI::Workflow { + using namespace AppInstaller::Repository; + using namespace Manifest; + void ReportDependencies::operator()(Execution::Context& context) const { if (!Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) @@ -19,22 +24,22 @@ namespace AppInstaller::CLI::Workflow { info << Resource::StringId(m_messageId) << std::endl; - if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsFeature)) + if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) { info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::WindowsFeature, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + dependencies.ApplyToType(DependencyType::WindowsFeature, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); } - if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsLibrary)) + if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) { info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::WindowsLibrary, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + dependencies.ApplyToType(DependencyType::WindowsLibrary, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); } - if (dependencies.HasAnyOf(Manifest::DependencyType::Package)) + if (dependencies.HasAnyOf(DependencyType::Package)) { info << " - " << Resource::String::PackageDependencies << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::Package, [&info](Manifest::Dependency dependency) + dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) { info << " " << dependency.Id; if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value() << "]"; @@ -42,10 +47,10 @@ namespace AppInstaller::CLI::Workflow }); } - if (dependencies.HasAnyOf(Manifest::DependencyType::External)) + if (dependencies.HasAnyOf(DependencyType::External)) { info << " - " << Resource::String::ExternalDependencies << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::External, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + dependencies.ApplyToType(DependencyType::External, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); } } } @@ -54,7 +59,7 @@ namespace AppInstaller::CLI::Workflow if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) { const auto& manifest = context.Get(); - Manifest::DependencyList allDependencies; + DependencyList allDependencies; for (const auto& installer : manifest.Installers) { @@ -82,7 +87,63 @@ namespace AppInstaller::CLI::Workflow if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) { // 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 - context.Add(Manifest::DependencyList()); // sending empty list of dependencies for now + context.Add(DependencyList()); // sending empty list of dependencies for now + } + } + + void OpenDependencySource(Execution::Context& context) + { + // two version: have a package version or a manifest + // openDependencySource, new context data "DependencySource" + // + //TODO change this, configure the source we want + context << OpenSource; + const auto& source = context.Get(); + /*const auto& source = context.Get()->GetLatestAvailableVersion()->GetSource(); + context.Add(std::move(source));*/ + } + + void BuildPackageDependenciesGraph(Execution::Context& context) + { + + const auto& dependencies = context.Get(); + + //TODO change this, configure the source we want + context << OpenSource; + const auto& source = context.Get(); + /*const auto& source = context.Get()->GetLatestAvailableVersion()->GetSource(); + context.Add(std::move(source));*/ + + std::vector toCheck; + dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + toCheck.push_back(dependency); + }); + + std::vector toInstall; + for (const auto& dependency : toCheck) + { + // search the package to see if it exists + SearchRequest searchRequest; + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependency.Id)); + const auto& matches = source->Search(searchRequest).Matches; + + if (!matches.empty()) + { + const auto& match = matches.at(0); // What to do if there's more than one? should not happen (report to the user) + const auto& installedVersion = match.Package->GetInstalledVersion(); + if (!installedVersion) + { + // get manifest and installer, maybe use something like vector as in InstallMultiple? + //how to choose best installer, should I use SelectInstaller or not? + //toInstall.push_back(PackagesAndInstallers(installer, )); + + //match.Package->GetLatestAvailableVersion()->GetManifest().Installers.at(0).Dependencies; + } + } } + + context.Reporter.Info() << "---" << std::endl; + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index e781411d47..7e388cc470 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -38,4 +38,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: Dependencies void GetDependenciesInfoForUninstall(Execution::Context& context); + + // Builds the dependency graph. + // Required Args: None + // Inputs: Dependencies + // Outputs: None + void BuildPackageDependenciesGraph(Execution::Context& context); } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 806bcd52ac..d446078c49 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -411,18 +411,10 @@ namespace AppInstaller::CLI::Workflow Workflow::EnsureApplicableInstaller << Workflow::GetDependenciesFromInstaller << Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + Workflow::BuildPackageDependenciesGraph << Workflow::InstallPackageInstaller; } - const struct PackagesAndInstallers - { - PackagesAndInstallers(std::optional inst, - AppInstaller::CLI::Execution::PackageToInstall pkg) : Installer(inst), Package(pkg) {} - - std::optional Installer; - AppInstaller::CLI::Execution::PackageToInstall Package; - }; - void InstallMultiple(Execution::Context& context) { bool allSucceeded = true; diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 30837e856c..98feca3304 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -106,4 +106,13 @@ namespace AppInstaller::CLI::Workflow // Inputs: ARPSnapshot?, Manifest, PackageVersion // Outputs: None void ReportARPChanges(Execution::Context& context); + + const struct PackagesAndInstallers + { + PackagesAndInstallers(std::optional inst, + AppInstaller::CLI::Execution::PackageToInstall pkg) : Installer(inst), Package(pkg) {} + + std::optional Installer; + AppInstaller::CLI::Execution::PackageToInstall Package; + }; } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index e767e8725b..2e42668ba9 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -237,6 +237,39 @@ namespace } }; + struct DependenciesTestSource : public TestSource + { + SearchResult Search(const SearchRequest& request) const override + { + SearchResult result; + + std::string input; + + if (request.Query) + { + input = request.Query->Value; + } + + if (input == "A.Dep.B") + { + ManifestInstaller installer; + installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); + + Manifest manifest; + manifest.Id = "A.Dep.B"; + manifest.Installers.push_back(installer); + + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + std::vector{ manifest }, + this->shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); + } + } + }; + struct TestContext; struct WorkflowTaskOverride From 160242bcd11fc95f672020de954a9ddda4638885 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Tue, 6 Jul 2021 12:53:11 -0300 Subject: [PATCH 02/41] proto tests --- .../ExecutionContextData.h | 7 ++ .../Workflows/DependenciesFlow.cpp | 103 ++++++++++----- .../Workflows/DependenciesFlow.h | 10 +- .../Workflows/InstallFlow.cpp | 3 +- src/AppInstallerCLITests/WorkFlow.cpp | 119 +++++++++++++++--- 5 files changed, 194 insertions(+), 48 deletions(-) diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index e0d8b63d57..414cb47699 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -48,6 +48,7 @@ namespace AppInstaller::CLI::Execution Sources, ARPSnapshot, Dependencies, + DependencySource, Max }; @@ -191,5 +192,11 @@ namespace AppInstaller::CLI::Execution { using value_t = Manifest::DependencyList; }; + + template <> + struct DataMapping + { + using value_t = std::shared_ptr; + }; } } diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 49a0931034..0b81ef5851 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -4,7 +4,6 @@ #include "pch.h" #include "DependenciesFlow.h" #include "ManifestComparator.h" -#include "InstallFlow.h" namespace AppInstaller::CLI::Workflow { @@ -93,52 +92,96 @@ namespace AppInstaller::CLI::Workflow void OpenDependencySource(Execution::Context& context) { - // two version: have a package version or a manifest - // openDependencySource, new context data "DependencySource" - // - //TODO change this, configure the source we want - context << OpenSource; - const auto& source = context.Get(); - /*const auto& source = context.Get()->GetLatestAvailableVersion()->GetSource(); - context.Add(std::move(source));*/ + // two options: have a package version or a manifest + // try to get source from package version + const auto& packageVersion = context.Get(); + + if (packageVersion) + { + // TODO open composite source: package + installed + // how to execute without progress? + //auto installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); + //auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); + auto compositeSource = packageVersion->GetSource(); + context.Add(compositeSource); + } + else + { + // TODO question to John: can/should we do nothing for local manifests? or set up something like --dependency-source + const auto& manifest = context.Get(); + } } void BuildPackageDependenciesGraph(Execution::Context& context) { - - const auto& dependencies = context.Get(); - - //TODO change this, configure the source we want - context << OpenSource; - const auto& source = context.Get(); - /*const auto& source = context.Get()->GetLatestAvailableVersion()->GetSource(); - context.Add(std::move(source));*/ + context << OpenDependencySource; + const auto& source = context.Get(); std::vector toCheck; - dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) - { - toCheck.push_back(dependency); - }); - std::vector toInstall; + // should this dictionary have Dependency as key? (to have min version) in this case Dependency should implement equal + std::map> dependencyGraph; // < package Id, dependencies > + std::map> inverseDependencyGraph; // < package Id, packages that depends on this one> + + const auto& installer = context.Get(); + if (installer) + { + dependencyGraph[installer->ProductId] = std::vector(); // ProductId is the same Id as the one used by Dependencies? + installer->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + toCheck.push_back(dependency); + dependencyGraph[dependency.Id] = std::vector(); + dependencyGraph[installer->ProductId].push_back(dependency); + + inverseDependencyGraph[dependency.Id] = std::vector{ installer->ProductId }; + }); + } // TODO fail otherwise + for (const auto& dependency : toCheck) { - // search the package to see if it exists + // search the package source+installed to see if the dep exists SearchRequest searchRequest; searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependency.Id)); const auto& matches = source->Search(searchRequest).Matches; if (!matches.empty()) { - const auto& match = matches.at(0); // What to do if there's more than one? should not happen (report to the user) - const auto& installedVersion = match.Package->GetInstalledVersion(); - if (!installedVersion) + const auto& match = matches.at(0); // What to do if there's more than one? TODO should not happen (report to the user) + const auto& package = match.Package; + if (!package->GetInstalledVersion()) { - // get manifest and installer, maybe use something like vector as in InstallMultiple? + const auto& packageVersion = package->GetLatestAvailableVersion(); //how to choose best installer, should I use SelectInstaller or not? - //toInstall.push_back(PackagesAndInstallers(installer, )); - - //match.Package->GetLatestAvailableVersion()->GetManifest().Installers.at(0).Dependencies; + const auto& installer = packageVersion->GetManifest().Installers.at(0); // fail if there's no installer? + const auto& dependencies = installer.Dependencies; + auto packageId = installer.ProductId; // ProductId is the same Id as the one used by Dependencies? + + // TODO save installers for later maybe? + dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + dependencyGraph[packageId].push_back(dependency); + + auto search = dependencyGraph.find(dependency.Id); + if (search == dependencyGraph.end()) // if not found + { + toCheck.push_back(dependency); + dependencyGraph[dependency.Id] = std::vector(); + + inverseDependencyGraph[dependency.Id] = std::vector{ packageId }; + } + else + { + // we only need to check for loops if the dependency already existed, right? + // should we have an inverse map? i.e., < id, packages that depend on this one > + // that can make searching for loops easier + + inverseDependencyGraph[dependency.Id].push_back(packageId); + bool hasLoop = false; + auto searchLoop = inverseDependencyGraph[dependency.Id]; + + + } + }); } } } diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 7e388cc470..67eb8b70f1 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -41,7 +41,13 @@ namespace AppInstaller::CLI::Workflow // Builds the dependency graph. // Required Args: None - // Inputs: Dependencies - // Outputs: None + // Inputs: DependencySource + // Outputs: Dependencies void BuildPackageDependenciesGraph(Execution::Context& context); + + // Sets up the source used to get the dependencies. + // Required Args: None + // Inputs: PackageVersion, Manifest + // Outputs: DependencySource + void OpenDependencySource(Execution::Context& context); } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index f999d450e7..1be6c27851 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -415,9 +415,8 @@ namespace AppInstaller::CLI::Workflow Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << - Workflow::GetDependenciesFromInstaller << - Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::BuildPackageDependenciesGraph << + Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::InstallPackageInstaller; } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 2e42668ba9..658d369e0c 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -248,25 +248,85 @@ namespace if (request.Query) { input = request.Query->Value; + } // else: default? + + ManifestInstaller installer; + Manifest manifest; + manifest.Installers.push_back(installer); + manifest.Id = input; + + /* + * Dependencies: + * "A": Depends on the test + * B: NoDeph + * C: B + * D: E + * E: D + * F: B + * G: C + * H: G, B + */ + + //-- predefined + if (input == "C") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); } - - if (input == "A.Dep.B") + if (input == "D") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "E")); + } + if (input == "E") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "D")); + } + if (input == "F") { - ManifestInstaller installer; + installer.Dependencies.Add(Dependency(DependencyType::Package, "D")); + } + if (input == "G") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "C")); + } + if (input == "H") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "G")); installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); + } - Manifest manifest; - manifest.Id = "A.Dep.B"; - manifest.Installers.push_back(installer); - - result.Matches.emplace_back( - ResultMatch( - TestPackage::Make( - std::vector{ manifest }, - this->shared_from_this() - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); + // depends on test + if (input == "StackOrderIsOk") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "C")); + } + if (input == "NeedsToInstallBFirst") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "C")); + } + if (input == "EasyToSeeLoop") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "D")); + } + if (input == "DependencyAlreadyInStackButNoLoop") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "C")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "F")); + } + if (input == "PathBetweenBranchesButNoLoop") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "C")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "H")); } + + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + std::vector{ manifest }, + this->shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); + return result; } }; @@ -394,6 +454,14 @@ void OverrideForImportSource(TestContext& context) } }); } +void OverrideForDependencySource(TestContext& context) +{ + context.Override({ "OpenDependencySource", [](TestContext& context) + { + context.Add(std::shared_ptr{ std::make_shared() }); + } }); +} + void OverrideForUpdateInstallerMotw(TestContext& context) { context.Override({ UpdateInstallerFileMotwIfApplicable, [](TestContext&) @@ -1685,6 +1753,29 @@ TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); } +TEST_CASE("InstallFlow_DependencyGraph", "[InstallFlow][workflow][dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForDependencySource(context); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); +} + TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") { std::ostringstream validateOutput; From 06ddcd659b7bfba161da40215217f01901beb59c Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 7 Jul 2021 18:10:26 -0300 Subject: [PATCH 03/41] testing --- .../Workflows/DependenciesFlow.cpp | 73 ++++++++++++++----- .../Workflows/DependenciesFlow.h | 3 + .../Workflows/InstallFlow.cpp | 2 +- src/AppInstallerCLITests/WorkFlow.cpp | 31 ++++++-- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 0b81ef5851..94535637c8 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -108,7 +108,7 @@ namespace AppInstaller::CLI::Workflow else { // TODO question to John: can/should we do nothing for local manifests? or set up something like --dependency-source - const auto& manifest = context.Get(); + //const auto& manifest = context.Get(); } } @@ -121,7 +121,6 @@ namespace AppInstaller::CLI::Workflow // should this dictionary have Dependency as key? (to have min version) in this case Dependency should implement equal std::map> dependencyGraph; // < package Id, dependencies > - std::map> inverseDependencyGraph; // < package Id, packages that depends on this one> const auto& installer = context.Get(); if (installer) @@ -133,15 +132,14 @@ namespace AppInstaller::CLI::Workflow dependencyGraph[dependency.Id] = std::vector(); dependencyGraph[installer->ProductId].push_back(dependency); - inverseDependencyGraph[dependency.Id] = std::vector{ installer->ProductId }; }); } // TODO fail otherwise - for (const auto& dependency : toCheck) + for (const auto& dependencyNode : toCheck) { // search the package source+installed to see if the dep exists SearchRequest searchRequest; - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependency.Id)); + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependencyNode.Id)); const auto& matches = source->Search(searchRequest).Matches; if (!matches.empty()) @@ -150,43 +148,78 @@ namespace AppInstaller::CLI::Workflow const auto& package = match.Package; if (!package->GetInstalledVersion()) { - const auto& packageVersion = package->GetLatestAvailableVersion(); + const auto& packageVersion = package->GetLatestAvailableVersion(); //TODO check availability before using //how to choose best installer, should I use SelectInstaller or not? - const auto& installer = packageVersion->GetManifest().Installers.at(0); // fail if there's no installer? - const auto& dependencies = installer.Dependencies; - auto packageId = installer.ProductId; // ProductId is the same Id as the one used by Dependencies? + const auto& packageVersionManifest = packageVersion->GetManifest(); + const auto& matchInstaller = packageVersionManifest.Installers.at(0); // fail if there's no installer? + const auto& matchDependencies = matchInstaller.Dependencies; + auto matchId = matchInstaller.ProductId; // ProductId is the same Id as the one used by Dependencies? // TODO save installers for later maybe? - dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { - dependencyGraph[packageId].push_back(dependency); + dependencyGraph[matchId].push_back(dependency); auto search = dependencyGraph.find(dependency.Id); if (search == dependencyGraph.end()) // if not found { toCheck.push_back(dependency); dependencyGraph[dependency.Id] = std::vector(); - - inverseDependencyGraph[dependency.Id] = std::vector{ packageId }; } else { // we only need to check for loops if the dependency already existed, right? // should we have an inverse map? i.e., < id, packages that depend on this one > - // that can make searching for loops easier - - inverseDependencyGraph[dependency.Id].push_back(packageId); - bool hasLoop = false; - auto searchLoop = inverseDependencyGraph[dependency.Id]; - - + if (graphHasLoop(dependencyGraph)) + { + context.Reporter.Info() << "has loop" << std::endl; + //TODO warn user and raise error + } } }); } + // TODO else: save information on dependencies already installed to inform the user? } } context.Reporter.Info() << "---" << std::endl; } + + // TODO make them iterative + // is there a better way that this to check for loops? + bool graphHasLoop(std::map> dependencyGraph) + { + for (const auto& node : dependencyGraph) { + auto visited = std::set(); + visited.insert(node.first); + if (hasLoopDFS(visited, node.first, dependencyGraph)) + { + return true; + } + } + return false; + } + + bool hasLoopDFS(std::set visited, string_t nodeId, std::map> dependencyGraph) + { + for (const auto& adjacent : dependencyGraph[nodeId]) + { + auto search = visited.find(adjacent.Id); + if (search == visited.end()) // if not found + { + visited.insert(adjacent.Id); + if (hasLoopDFS(visited, adjacent.Id, dependencyGraph)) + { + return true; + } + } + else + { + return true; + } + } + + return false; + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 67eb8b70f1..1d3ca0fb91 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -50,4 +50,7 @@ namespace AppInstaller::CLI::Workflow // Inputs: PackageVersion, Manifest // Outputs: DependencySource void OpenDependencySource(Execution::Context& context); + + bool graphHasLoop(std::map> dependencyGraph); + bool hasLoopDFS(std::set visited, AppInstaller::Manifest::string_t nodeId, std::map> dependencyGraph); } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 1be6c27851..d4fe70d361 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -416,7 +416,7 @@ namespace AppInstaller::CLI::Workflow Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << Workflow::BuildPackageDependenciesGraph << - Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + //Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::InstallPackageInstaller; } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 658d369e0c..25112039f1 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -248,12 +248,24 @@ namespace if (request.Query) { input = request.Query->Value; - } // else: default? + } + else if (!request.Inclusions.empty()) + { + input = request.Inclusions[0].Value; + } + else if (!request.Filters.empty()) + { + input = request.Filters[0].Value; + }// else: default? - ManifestInstaller installer; - Manifest manifest; - manifest.Installers.push_back(installer); + auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Exe_Dependencies.yaml")); manifest.Id = input; + manifest.Moniker = input; + // TODO maybe change package name on default locale for better debbugging + + auto& installer = manifest.Installers.at(0); + installer.Dependencies.Clear(); + installer.ProductId = input; /* * Dependencies: @@ -456,9 +468,14 @@ void OverrideForImportSource(TestContext& context) void OverrideForDependencySource(TestContext& context) { + context.Override({ Workflow::OpenSource, [](TestContext& context) + { + context.Add(std::make_shared()); + } }); + context.Override({ "OpenDependencySource", [](TestContext& context) { - context.Add(std::shared_ptr{ std::make_shared() }); + context.Add(std::make_shared()); } }); } @@ -1762,7 +1779,7 @@ TEST_CASE("InstallFlow_DependencyGraph", "[InstallFlow][workflow][dependencies]" OverrideForDependencySource(context); OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); + context.Args.AddArg(Execution::Args::Type::Query, "EasyToSeeLoop"sv); TestUserSettings settings; settings.Set({ true }); @@ -1773,7 +1790,7 @@ TEST_CASE("InstallFlow_DependencyGraph", "[InstallFlow][workflow][dependencies]" // Verify all types of dependencies are printed REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); - REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); + REQUIRE(installOutput.str().find("has loop") != std::string::npos); } TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") From e6d7b8256c9e478410995b29262c1f89ac85584c Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 8 Jul 2021 14:58:51 -0300 Subject: [PATCH 04/41] questions --- src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 94535637c8..8508e4c585 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -120,7 +120,7 @@ namespace AppInstaller::CLI::Workflow std::vector toCheck; // should this dictionary have Dependency as key? (to have min version) in this case Dependency should implement equal - std::map> dependencyGraph; // < package Id, dependencies > + std::map> dependencyGraph; // < package Id, dependencies > value should be a set instead of a vector? const auto& installer = context.Get(); if (installer) @@ -170,6 +170,7 @@ namespace AppInstaller::CLI::Workflow { // we only need to check for loops if the dependency already existed, right? // should we have an inverse map? i.e., < id, packages that depend on this one > + // or should we check for loops only once (when we have all deps in the graph) if (graphHasLoop(dependencyGraph)) { context.Reporter.Info() << "has loop" << std::endl; @@ -185,6 +186,7 @@ namespace AppInstaller::CLI::Workflow context.Reporter.Info() << "---" << std::endl; } + // TODO get dependency installation order from dependencyGraph (topological order) // TODO make them iterative // is there a better way that this to check for loops? @@ -208,7 +210,7 @@ namespace AppInstaller::CLI::Workflow auto search = visited.find(adjacent.Id); if (search == visited.end()) // if not found { - visited.insert(adjacent.Id); + visited.insert(adjacent.Id); //like this is ok or should we insert to a copy? if (hasLoopDFS(visited, adjacent.Id, dependencyGraph)) { return true; From 62ef1c7a7cc7c2e8f7a72a9bf6bb0f9cb35e9145 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 12 Jul 2021 13:58:27 -0300 Subject: [PATCH 05/41] test for dependency graph and fixes --- .../Workflows/DependenciesFlow.cpp | 50 ++++++++++++----- .../Workflows/DependenciesFlow.h | 2 +- src/AppInstallerCLITests/WorkFlow.cpp | 53 +++++++++++++++---- .../Public/winget/ManifestCommon.h | 6 ++- 4 files changed, 87 insertions(+), 24 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 8508e4c585..f365ee419f 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -120,7 +120,8 @@ namespace AppInstaller::CLI::Workflow std::vector toCheck; // should this dictionary have Dependency as key? (to have min version) in this case Dependency should implement equal - std::map> dependencyGraph; // < package Id, dependencies > value should be a set instead of a vector? + std::map> dependencyGraph; + // < package Id, dependencies > value should be a set instead of a vector? const auto& installer = context.Get(); if (installer) @@ -135,30 +136,47 @@ namespace AppInstaller::CLI::Workflow }); } // TODO fail otherwise - for (const auto& dependencyNode : toCheck) + std::map failedPackages; + + for (int i = 0; i < toCheck.size(); ++i) { - // search the package source+installed to see if the dep exists + const auto& dependencyNode = toCheck.at(i); + auto packageID = dependencyNode.Id; + SearchRequest searchRequest; - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependencyNode.Id)); + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, packageID)); const auto& matches = source->Search(searchRequest).Matches; if (!matches.empty()) { - const auto& match = matches.at(0); // What to do if there's more than one? TODO should not happen (report to the user) + const auto& match = matches.at(0); + if (matches.size() > 1) { + failedPackages[packageID] = "Too many matches"; //TODO localize all errors + continue; + } + const auto& package = match.Package; if (!package->GetInstalledVersion()) { - const auto& packageVersion = package->GetLatestAvailableVersion(); //TODO check availability before using - //how to choose best installer, should I use SelectInstaller or not? + const auto& packageVersion = package->GetLatestAvailableVersion(); + if (!packageVersion) { + failedPackages[packageID] = "No package version found"; //TODO localize all errors + continue; + } const auto& packageVersionManifest = packageVersion->GetManifest(); - const auto& matchInstaller = packageVersionManifest.Installers.at(0); // fail if there's no installer? + if (packageVersionManifest.Installers.empty()) { + failedPackages[packageID] = "No installers found"; //TODO localize all errors + continue; + } + //how to choose best installer, should I use SelectInstaller or not? + const auto& matchInstaller = packageVersionManifest.Installers.at(0); const auto& matchDependencies = matchInstaller.Dependencies; - auto matchId = matchInstaller.ProductId; // ProductId is the same Id as the one used by Dependencies? // TODO save installers for later maybe? matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { - dependencyGraph[matchId].push_back(dependency); + // TODO check dependency min version is <= latest version + dependencyGraph[packageID].push_back(dependency); auto search = dependencyGraph.find(dependency.Id); if (search == dependencyGraph.end()) // if not found @@ -181,6 +199,11 @@ namespace AppInstaller::CLI::Workflow } // TODO else: save information on dependencies already installed to inform the user? } + else + { + failedPackages[packageID] = "No matches"; //TODO localize all errors + continue; + } } context.Reporter.Info() << "---" << std::endl; @@ -203,15 +226,16 @@ namespace AppInstaller::CLI::Workflow return false; } - bool hasLoopDFS(std::set visited, string_t nodeId, std::map> dependencyGraph) + bool hasLoopDFS(std::set visited, const string_t& nodeId, std::map>& dependencyGraph) { for (const auto& adjacent : dependencyGraph[nodeId]) { auto search = visited.find(adjacent.Id); if (search == visited.end()) // if not found { - visited.insert(adjacent.Id); //like this is ok or should we insert to a copy? - if (hasLoopDFS(visited, adjacent.Id, dependencyGraph)) + auto newVisited = visited; + newVisited.insert(adjacent.Id); + if (hasLoopDFS(newVisited, adjacent.Id, dependencyGraph)) { return true; } diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 1d3ca0fb91..62a125c039 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -52,5 +52,5 @@ namespace AppInstaller::CLI::Workflow void OpenDependencySource(Execution::Context& context); bool graphHasLoop(std::map> dependencyGraph); - bool hasLoopDFS(std::set visited, AppInstaller::Manifest::string_t nodeId, std::map> dependencyGraph); + bool hasLoopDFS(std::set visited, const AppInstaller::Manifest::string_t& nodeId, std::map>& dependencyGraph); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 25112039f1..448975d996 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -294,7 +294,7 @@ namespace } if (input == "F") { - installer.Dependencies.Add(Dependency(DependencyType::Package, "D")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); } if (input == "G") { @@ -472,11 +472,6 @@ void OverrideForDependencySource(TestContext& context) { context.Add(std::make_shared()); } }); - - context.Override({ "OpenDependencySource", [](TestContext& context) - { - context.Add(std::make_shared()); - } }); } void OverrideForUpdateInstallerMotw(TestContext& context) @@ -1770,7 +1765,7 @@ TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); } -TEST_CASE("InstallFlow_DependencyGraph", "[InstallFlow][workflow][dependencies]") +TEST_CASE("DependencyGraph_Loop", "[InstallFlow][workflow][dependencyGraph]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1788,11 +1783,51 @@ TEST_CASE("InstallFlow_DependencyGraph", "[InstallFlow][workflow][dependencies]" install.Execute(context); INFO(installOutput.str()); - // Verify all types of dependencies are printed - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); REQUIRE(installOutput.str().find("has loop") != std::string::npos); } +TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGraph]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForDependencySource(context); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Query, "DependencyAlreadyInStackButNoLoop"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find("has loop") == std::string::npos); +} + +TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForDependencySource(context); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Query, "PathBetweenBranchesButNoLoop"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find("has loop") == std::string::npos); +} + TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") { std::ostringstream validateOutput; diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 49153c0601..a7e8134f80 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -129,6 +129,10 @@ namespace AppInstaller::Manifest Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(std::move(minVersion)) {} Dependency(DependencyType type, string_t id) : Type(std::move(type)), Id(std::move(id)) {} Dependency(DependencyType type) : Type(type) {} + + bool operator==(const Dependency& rhs) const { + return Type == rhs.Type && ICUCaseInsensitiveEquals(Id, rhs.Id); + } }; struct DependencyList @@ -184,7 +188,7 @@ namespace AppInstaller::Manifest Dependency* HasDependency(const Dependency& dependencyToSearch) { for (auto& dependency : dependencies) { - if (dependency.Type == dependencyToSearch.Type && ICUCaseInsensitiveEquals(dependency.Id, dependencyToSearch.Id)) + if (dependency == dependencyToSearch) { return &dependency; } From 0ed66ee227802f205f7e317b27c28c29e19702fc Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 12 Jul 2021 15:31:42 -0300 Subject: [PATCH 06/41] check loop logic change --- .../Workflows/DependenciesFlow.cpp | 21 ++++++++----------- .../Workflows/DependenciesFlow.h | 2 +- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index f365ee419f..f84a9a9c3e 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -189,7 +189,7 @@ namespace AppInstaller::CLI::Workflow // we only need to check for loops if the dependency already existed, right? // should we have an inverse map? i.e., < id, packages that depend on this one > // or should we check for loops only once (when we have all deps in the graph) - if (graphHasLoop(dependencyGraph)) + if (graphHasLoop(dependencyGraph, installer->ProductId)) { context.Reporter.Info() << "has loop" << std::endl; //TODO warn user and raise error @@ -213,29 +213,26 @@ namespace AppInstaller::CLI::Workflow // TODO make them iterative // is there a better way that this to check for loops? - bool graphHasLoop(std::map> dependencyGraph) + bool graphHasLoop(std::map> dependencyGraph, const string_t& root) { - for (const auto& node : dependencyGraph) { - auto visited = std::set(); - visited.insert(node.first); - if (hasLoopDFS(visited, node.first, dependencyGraph)) - { - return true; - } + auto visited = std::set(); + visited.insert(root); + if (hasLoopDFS(visited, root, dependencyGraph)) + { + return true; } return false; } bool hasLoopDFS(std::set visited, const string_t& nodeId, std::map>& dependencyGraph) { + visited.insert(nodeId); for (const auto& adjacent : dependencyGraph[nodeId]) { auto search = visited.find(adjacent.Id); if (search == visited.end()) // if not found { - auto newVisited = visited; - newVisited.insert(adjacent.Id); - if (hasLoopDFS(newVisited, adjacent.Id, dependencyGraph)) + if (hasLoopDFS(visited, adjacent.Id, dependencyGraph)) { return true; } diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 62a125c039..006561eb3c 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -51,6 +51,6 @@ namespace AppInstaller::CLI::Workflow // Outputs: DependencySource void OpenDependencySource(Execution::Context& context); - bool graphHasLoop(std::map> dependencyGraph); + bool graphHasLoop(std::map> dependencyGraph, const AppInstaller::Manifest::string_t& root); bool hasLoopDFS(std::set visited, const AppInstaller::Manifest::string_t& nodeId, std::map>& dependencyGraph); } \ No newline at end of file From 6eb87bf8293dce5103bbbe9b0b7ca581f15e90ac Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 12 Jul 2021 15:41:11 -0300 Subject: [PATCH 07/41] const & dep graph --- .../Workflows/DependenciesFlow.cpp | 28 ++++++++----------- .../Workflows/DependenciesFlow.h | 9 ++++-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index f84a9a9c3e..bab358edd3 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -183,17 +183,6 @@ namespace AppInstaller::CLI::Workflow { toCheck.push_back(dependency); dependencyGraph[dependency.Id] = std::vector(); - } - else - { - // we only need to check for loops if the dependency already existed, right? - // should we have an inverse map? i.e., < id, packages that depend on this one > - // or should we check for loops only once (when we have all deps in the graph) - if (graphHasLoop(dependencyGraph, installer->ProductId)) - { - context.Reporter.Info() << "has loop" << std::endl; - //TODO warn user and raise error - } } }); } @@ -205,34 +194,39 @@ namespace AppInstaller::CLI::Workflow continue; } } + auto order = std::vector(); + if (graphHasLoop(dependencyGraph, installer->ProductId, order)) + { + context.Reporter.Info() << "has loop" << std::endl; + //TODO warn user and raise error + } context.Reporter.Info() << "---" << std::endl; } - // TODO get dependency installation order from dependencyGraph (topological order) // TODO make them iterative // is there a better way that this to check for loops? - bool graphHasLoop(std::map> dependencyGraph, const string_t& root) + bool graphHasLoop(const std::map>& dependencyGraph, const string_t& root, std::vector& order) { auto visited = std::set(); visited.insert(root); - if (hasLoopDFS(visited, root, dependencyGraph)) + if (hasLoopDFS(visited, root, dependencyGraph, order)) { return true; } return false; } - bool hasLoopDFS(std::set visited, const string_t& nodeId, std::map>& dependencyGraph) + bool hasLoopDFS(std::set visited, const string_t& nodeId, const std::map>& dependencyGraph, std::vector& order) { visited.insert(nodeId); - for (const auto& adjacent : dependencyGraph[nodeId]) + for (const auto& adjacent : dependencyGraph.at(nodeId)) { auto search = visited.find(adjacent.Id); if (search == visited.end()) // if not found { - if (hasLoopDFS(visited, adjacent.Id, dependencyGraph)) + if (hasLoopDFS(visited, adjacent.Id, dependencyGraph, order)) { return true; } diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 006561eb3c..10c5dd4774 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -51,6 +51,11 @@ namespace AppInstaller::CLI::Workflow // Outputs: DependencySource void OpenDependencySource(Execution::Context& context); - bool graphHasLoop(std::map> dependencyGraph, const AppInstaller::Manifest::string_t& root); - bool hasLoopDFS(std::set visited, const AppInstaller::Manifest::string_t& nodeId, std::map>& dependencyGraph); + bool graphHasLoop(const std::map>& dependencyGraph, + const AppInstaller::Manifest::string_t& root, + std::vector& order); + bool hasLoopDFS(std::set visited, + const AppInstaller::Manifest::string_t& nodeId, + const std::map>& dependencyGraph, + std::vector& order); } \ No newline at end of file From d69b3e3f213bc551f544f0037645d8fbc8573a90 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 12 Jul 2021 16:08:53 -0300 Subject: [PATCH 08/41] with installation order --- .../Workflows/DependenciesFlow.cpp | 20 ++++++++++++++----- src/AppInstallerCLITests/WorkFlow.cpp | 3 +++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index bab358edd3..39948c9752 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -194,15 +194,20 @@ namespace AppInstaller::CLI::Workflow continue; } } - auto order = std::vector(); - if (graphHasLoop(dependencyGraph, installer->ProductId, order)) + auto info = context.Reporter.Info(); + auto installationOrder = std::vector(); + if (graphHasLoop(dependencyGraph, installer->ProductId, installationOrder)) { - context.Reporter.Info() << "has loop" << std::endl; + info << "has loop" << std::endl; //TODO warn user and raise error } - context.Reporter.Info() << "---" << std::endl; - + info << "order: "; + for (auto const& nodeId : installationOrder) + { + info << nodeId << ", "; + } + info << std::endl; } // TODO make them iterative @@ -236,6 +241,11 @@ namespace AppInstaller::CLI::Workflow return true; } } + + if (std::find(order.begin(), order.end(), nodeId) == order.end()) + { + order.push_back(nodeId); + } return false; } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 448975d996..d881d1f7cb 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -1805,6 +1805,7 @@ TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGr INFO(installOutput.str()); REQUIRE(installOutput.str().find("has loop") == std::string::npos); + REQUIRE(installOutput.str().find("B, C, F, DependencyAlreadyInStackButNoLoop,") != std::string::npos); } TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph]") @@ -1826,6 +1827,8 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph INFO(installOutput.str()); REQUIRE(installOutput.str().find("has loop") == std::string::npos); + REQUIRE(installOutput.str().find("order: B, C, G, H, PathBetweenBranchesButNoLoop,") != std::string::npos); + } TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") From b67708c591d882a55c93d647468ac51bdbff818f Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 12 Jul 2021 16:13:42 -0300 Subject: [PATCH 09/41] two more tests --- src/AppInstallerCLITests/WorkFlow.cpp | 46 +++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index d881d1f7cb..d011b8bfef 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -1831,6 +1831,52 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph } +TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForDependencySource(context); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find("has loop") == std::string::npos); + REQUIRE(installOutput.str().find("order: B, C, StackOrderIsOk,") != std::string::npos); + +} + +TEST_CASE("DependencyGraph_BFirst", "[InstallFlow][workflow][dependencyGraph]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForDependencySource(context); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Query, "NeedsToInstallBFirst"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find("has loop") == std::string::npos); + REQUIRE(installOutput.str().find("order: B, C, NeedsToInstallBFirst,") != std::string::npos); + +} + TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") { std::ostringstream validateOutput; From d5f60503e2504c6f10a5139654d55812f6e3b47f Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 12 Jul 2021 17:27:01 -0300 Subject: [PATCH 10/41] change map key to Dependency (missing min version check) --- .../Workflows/DependenciesFlow.cpp | 61 ++++++++++--------- .../Workflows/DependenciesFlow.h | 14 ++--- 2 files changed, 39 insertions(+), 36 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 39948c9752..d2137a9a42 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -119,19 +119,20 @@ namespace AppInstaller::CLI::Workflow std::vector toCheck; - // should this dictionary have Dependency as key? (to have min version) in this case Dependency should implement equal - std::map> dependencyGraph; + std::map> dependencyGraph; // < package Id, dependencies > value should be a set instead of a vector? const auto& installer = context.Get(); + Dependency rootDependency = Dependency(DependencyType::Package); if (installer) { - dependencyGraph[installer->ProductId] = std::vector(); // ProductId is the same Id as the one used by Dependencies? + rootDependency.Id = installer->ProductId; // ProductId is the same Id as the one used by Dependencies? + dependencyGraph[rootDependency] = std::vector(); installer->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { toCheck.push_back(dependency); - dependencyGraph[dependency.Id] = std::vector(); - dependencyGraph[installer->ProductId].push_back(dependency); + dependencyGraph[dependency] = std::vector(); + dependencyGraph[rootDependency].push_back(dependency); }); } // TODO fail otherwise @@ -140,18 +141,17 @@ namespace AppInstaller::CLI::Workflow for (int i = 0; i < toCheck.size(); ++i) { - const auto& dependencyNode = toCheck.at(i); - auto packageID = dependencyNode.Id; + auto dependencyNode = toCheck.at(i); SearchRequest searchRequest; - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, packageID)); + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependencyNode.Id)); const auto& matches = source->Search(searchRequest).Matches; if (!matches.empty()) { const auto& match = matches.at(0); if (matches.size() > 1) { - failedPackages[packageID] = "Too many matches"; //TODO localize all errors + failedPackages[dependencyNode.Id] = "Too many matches"; //TODO localize all errors continue; } @@ -160,61 +160,64 @@ namespace AppInstaller::CLI::Workflow { const auto& packageVersion = package->GetLatestAvailableVersion(); if (!packageVersion) { - failedPackages[packageID] = "No package version found"; //TODO localize all errors + failedPackages[dependencyNode.Id] = "No package version found"; //TODO localize all errors continue; } const auto& packageVersionManifest = packageVersion->GetManifest(); if (packageVersionManifest.Installers.empty()) { - failedPackages[packageID] = "No installers found"; //TODO localize all errors + failedPackages[dependencyNode.Id] = "No installers found"; //TODO localize all errors continue; } //how to choose best installer, should I use SelectInstaller or not? const auto& matchInstaller = packageVersionManifest.Installers.at(0); const auto& matchDependencies = matchInstaller.Dependencies; + // TODO check dependency min version is <= latest version + // TODO save installers for later maybe? matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { - // TODO check dependency min version is <= latest version - dependencyGraph[packageID].push_back(dependency); + dependencyGraph[dependencyNode].push_back(dependency); - auto search = dependencyGraph.find(dependency.Id); + auto search = dependencyGraph.find(dependency); if (search == dependencyGraph.end()) // if not found { toCheck.push_back(dependency); - dependencyGraph[dependency.Id] = std::vector(); + dependencyGraph[dependency] = std::vector(); } }); } // TODO else: save information on dependencies already installed to inform the user? + // TODO check dependency min version is <= installed version (otherwise update? -> should check for new dependencies) + } else { - failedPackages[packageID] = "No matches"; //TODO localize all errors + failedPackages[dependencyNode.Id] = "No matches"; //TODO localize all errors continue; } } auto info = context.Reporter.Info(); - auto installationOrder = std::vector(); - if (graphHasLoop(dependencyGraph, installer->ProductId, installationOrder)) + auto installationOrder = std::vector(); + if (graphHasLoop(dependencyGraph, rootDependency, installationOrder)) { info << "has loop" << std::endl; //TODO warn user and raise error } info << "order: "; - for (auto const& nodeId : installationOrder) + for (auto const& node : installationOrder) { - info << nodeId << ", "; + info << node.Id << ", "; } info << std::endl; } // TODO make them iterative // is there a better way that this to check for loops? - bool graphHasLoop(const std::map>& dependencyGraph, const string_t& root, std::vector& order) + bool graphHasLoop(const std::map>& dependencyGraph, const Dependency& root, std::vector& order) { - auto visited = std::set(); + auto visited = std::set(); visited.insert(root); if (hasLoopDFS(visited, root, dependencyGraph, order)) { @@ -223,15 +226,15 @@ namespace AppInstaller::CLI::Workflow return false; } - bool hasLoopDFS(std::set visited, const string_t& nodeId, const std::map>& dependencyGraph, std::vector& order) + bool hasLoopDFS(std::set visited, const Dependency& node, const std::map>& dependencyGraph, std::vector& order) { - visited.insert(nodeId); - for (const auto& adjacent : dependencyGraph.at(nodeId)) + visited.insert(node); + for (const auto& adjacent : dependencyGraph.at(node)) { - auto search = visited.find(adjacent.Id); + auto search = visited.find(adjacent); if (search == visited.end()) // if not found { - if (hasLoopDFS(visited, adjacent.Id, dependencyGraph, order)) + if (hasLoopDFS(visited, adjacent, dependencyGraph, order)) { return true; } @@ -242,9 +245,9 @@ namespace AppInstaller::CLI::Workflow } } - if (std::find(order.begin(), order.end(), nodeId) == order.end()) + if (std::find(order.begin(), order.end(), node) == order.end()) { - order.push_back(nodeId); + order.push_back(node); } return false; diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 10c5dd4774..abc66f7d0b 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -51,11 +51,11 @@ namespace AppInstaller::CLI::Workflow // Outputs: DependencySource void OpenDependencySource(Execution::Context& context); - bool graphHasLoop(const std::map>& dependencyGraph, - const AppInstaller::Manifest::string_t& root, - std::vector& order); - bool hasLoopDFS(std::set visited, - const AppInstaller::Manifest::string_t& nodeId, - const std::map>& dependencyGraph, - std::vector& order); + bool graphHasLoop(const std::map>& dependencyGraph, + const AppInstaller::Manifest::Dependency& root, + std::vector& order); + bool hasLoopDFS(std::set visited, + const AppInstaller::Manifest::Dependency& node, + const std::map>& dependencyGraph, + std::vector& order); } \ No newline at end of file From c737c8db398a4e4e2512e8cf64d700b8ae35ad63 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Tue, 13 Jul 2021 12:38:51 -0300 Subject: [PATCH 11/41] Dependency implements operand< --- src/AppInstallerCommonCore/Public/winget/ManifestCommon.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index a7e8134f80..4b1b6d8708 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -133,6 +133,11 @@ namespace AppInstaller::Manifest bool operator==(const Dependency& rhs) const { return Type == rhs.Type && ICUCaseInsensitiveEquals(Id, rhs.Id); } + + bool operator <(const Dependency& rhs) const + { + return Id < rhs.Id; + } }; struct DependencyList From 087dd8d04dd4c7ce058dcc9096dd23ff3e3440c6 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Tue, 13 Jul 2021 14:04:20 -0300 Subject: [PATCH 12/41] Dependency.MinVersion type is AppInstaller::Utility::Version --- .../Workflows/DependenciesFlow.cpp | 12 ++++++------ src/AppInstallerCLICore/Workflows/ShowFlow.cpp | 2 +- src/AppInstallerCLITests/WorkFlow.cpp | 1 - .../Manifest/ManifestYamlPopulator.cpp | 2 +- .../Public/winget/ManifestCommon.h | 12 +++++------- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index d2137a9a42..d8992a9f55 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -41,7 +41,7 @@ namespace AppInstaller::CLI::Workflow dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) { info << " " << dependency.Id; - if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value() << "]"; + if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value().ToString() << "]"; info << std::endl; }); } @@ -122,13 +122,13 @@ namespace AppInstaller::CLI::Workflow std::map> dependencyGraph; // < package Id, dependencies > value should be a set instead of a vector? - const auto& installer = context.Get(); - Dependency rootDependency = Dependency(DependencyType::Package); - if (installer) + const auto& rootInstaller = context.Get(); + const auto& rootManifest = context.Get(); + Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id); + if (rootInstaller) { - rootDependency.Id = installer->ProductId; // ProductId is the same Id as the one used by Dependencies? dependencyGraph[rootDependency] = std::vector(); - installer->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + rootInstaller->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { toCheck.push_back(dependency); dependencyGraph[dependency] = std::vector(); diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index e57da40893..12e967fa16 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -95,7 +95,7 @@ namespace AppInstaller::CLI::Workflow info << " - PackageDependencies: " << std::endl; dependencies.ApplyToType(Manifest::DependencyType::Package, [&info](Manifest::Dependency dependency) { info << " " << dependency.Id; - if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value() << "]"; + if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value().ToString() << "]"; info << std::endl; }); } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index d011b8bfef..0aa6c22c37 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -265,7 +265,6 @@ namespace auto& installer = manifest.Installers.at(0); installer.Dependencies.Clear(); - installer.ProductId = input; /* * Dependencies: diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 58b6c56cdb..79b1d07a3c 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -383,7 +383,7 @@ namespace AppInstaller::Manifest result = { { "PackageIdentifier", [this](const YAML::Node& value)->ValidationErrors { m_p_packageDependency->Id = Utility::Trim(value.as()); return {}; } }, - { "MinimumVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_packageDependency->MinVersion = Utility::Trim(value.as()); return {}; } }, + { "MinimumVersion", [this](const YAML::Node& value)->ValidationErrors { m_p_packageDependency->MinVersion = AppInstaller::Utility::Version(Utility::Trim(value.as())); return {}; } }, }; } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 4b1b6d8708..b64ac42b2f 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -124,9 +124,9 @@ namespace AppInstaller::Manifest { DependencyType Type; string_t Id; - std::optional MinVersion; + std::optional MinVersion; - Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(std::move(minVersion)) {} + Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(AppInstaller::Utility::Version(minVersion)) {} Dependency(DependencyType type, string_t id) : Type(std::move(type)), Id(std::move(id)) {} Dependency(DependencyType type) : Type(type) {} @@ -153,11 +153,9 @@ namespace AppInstaller::Manifest { if (existingDependency->MinVersion) { - const auto& newDependencyVersion = AppInstaller::Utility::Version(newDependency.MinVersion.value()); - const auto& existingDependencyVersion = AppInstaller::Utility::Version(existingDependency->MinVersion.value()); - if (newDependencyVersion > existingDependencyVersion) + if (newDependency.MinVersion.value() > existingDependency->MinVersion.value()) { - existingDependency->MinVersion.value() = newDependencyVersion.ToString(); + existingDependency->MinVersion.value() = newDependency.MinVersion.value(); } } else @@ -209,7 +207,7 @@ namespace AppInstaller::Manifest if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id, id)) { if (dependency.MinVersion) { - if (dependency.MinVersion.value() == minVersion) + if (dependency.MinVersion.value() == AppInstaller::Utility::Version(minVersion)) { return true; } From 46d9d0b3f27c731b002c3eac9d8bc8eca0eec43a Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Tue, 13 Jul 2021 14:34:33 -0300 Subject: [PATCH 13/41] choose installer using ManifestComparator --- .../Workflows/DependenciesFlow.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index d8992a9f55..1100569651 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -168,9 +168,18 @@ namespace AppInstaller::CLI::Workflow failedPackages[dependencyNode.Id] = "No installers found"; //TODO localize all errors continue; } - //how to choose best installer, should I use SelectInstaller or not? - const auto& matchInstaller = packageVersionManifest.Installers.at(0); - const auto& matchDependencies = matchInstaller.Dependencies; + + ManifestComparator manifestComparator(context.Args, packageVersion->GetMetadata()); + const auto& matchInstaller = manifestComparator.GetPreferredInstaller(packageVersionManifest); + + if (!matchInstaller) + { + failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors + continue; + } + + //const auto& matchInstaller = packageVersionManifest.Installers.at(0); + const auto& matchDependencies = matchInstaller.value().Dependencies; // TODO check dependency min version is <= latest version From c82f2340204d3e5bde19be909fa49140d2030f3f Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 14 Jul 2021 16:17:50 -0300 Subject: [PATCH 14/41] getting composite source --- .../Workflows/DependenciesFlow.cpp | 47 ++++++++++++------- .../Workflows/InstallFlow.cpp | 2 +- 2 files changed, 31 insertions(+), 18 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 1100569651..40648c2dc4 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -94,39 +94,41 @@ namespace AppInstaller::CLI::Workflow { // two options: have a package version or a manifest // try to get source from package version - const auto& packageVersion = context.Get(); - if (packageVersion) + //source = empty + //if PackageVersion then source = PV source + //else if Source exists then fail // if Source already open then this can't be an install -m or validate command + //else then OpenSource; source = Source + // result = CreateComposite(installed, source) + + if (context.Contains(Execution::Data::PackageVersion)) { - // TODO open composite source: package + installed - // how to execute without progress? - //auto installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); - //auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); - auto compositeSource = packageVersion->GetSource(); + const auto& packageVersion = context.Get(); + // how to execute without progress? should we? + const auto& installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); + auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); context.Add(compositeSource); } else { // TODO question to John: can/should we do nothing for local manifests? or set up something like --dependency-source - //const auto& manifest = context.Get(); + // Open source passed by parameter (from sourcename) + // openCompositeSource should work for getting installed+opened source } } void BuildPackageDependenciesGraph(Execution::Context& context) { - context << OpenDependencySource; - const auto& source = context.Get(); - - std::vector toCheck; - - std::map> dependencyGraph; - // < package Id, dependencies > value should be a set instead of a vector? - - const auto& rootInstaller = context.Get(); const auto& rootManifest = context.Get(); Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id); + + std::vector toCheck; + std::map> dependencyGraph; //(?) value should be a set instead of a vector? + const auto& rootInstaller = context.Get(); if (rootInstaller) { + context.Add(rootInstaller->Dependencies); // to use in report + // TODO remove this ^ if we are reporting dependencies somewhere else while installing/managing them dependencyGraph[rootDependency] = std::vector(); rootInstaller->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { @@ -137,6 +139,15 @@ namespace AppInstaller::CLI::Workflow }); } // TODO fail otherwise + context << OpenDependencySource; + if (!context.Contains(Execution::Data::DependencySource)) + { + context.Reporter.Info() << "dependency source not found" << std::endl; //TODO localize message + return; //TODO terminate with error once we can get source for dependencies, when a manifest was passed + //AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); // TODO create a new error code? + } + + const auto& source = context.Get(); std::map failedPackages; for (int i = 0; i < toCheck.size(); ++i) @@ -214,6 +225,8 @@ namespace AppInstaller::CLI::Workflow //TODO warn user and raise error } + // TODO raise error for failedPackages (if there's at least one) + info << "order: "; for (auto const& node : installationOrder) { diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index d4fe70d361..1be6c27851 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -416,7 +416,7 @@ namespace AppInstaller::CLI::Workflow Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << Workflow::BuildPackageDependenciesGraph << - //Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::InstallPackageInstaller; } From aebac6b9829a7d306a6f92b7ad0d25880353dc1d Mon Sep 17 00:00:00 2001 From: JohnMcPMS Date: Wed, 14 Jul 2021 14:36:25 -0700 Subject: [PATCH 15/41] Drop const from the returned type of GetSource --- src/AppInstallerCLITests/TestSource.cpp | 10 ++++---- src/AppInstallerCLITests/TestSource.h | 12 +++++----- src/AppInstallerCLITests/WorkFlow.cpp | 18 +++++++------- .../Microsoft/SQLiteIndexSource.cpp | 24 +++++++++---------- .../Public/AppInstallerRepositorySearch.h | 2 +- .../Rest/RestSource.cpp | 20 ++++++++-------- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index 0a7bfce688..d7f5025513 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -30,10 +30,10 @@ namespace TestCommon } } - TestPackageVersion::TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source) : + TestPackageVersion::TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source) : VersionManifest(manifest), Metadata(std::move(installationMetadata)), Source(source) {} - TestPackageVersion::TestPackageVersion(const Manifest& manifest, std::weak_ptr source) : + TestPackageVersion::TestPackageVersion(const Manifest& manifest, std::weak_ptr source) : VersionManifest(manifest), Source(source) {} TestPackageVersion::LocIndString TestPackageVersion::GetProperty(PackageVersionProperty property) const @@ -96,7 +96,7 @@ namespace TestCommon return VersionManifest; } - std::shared_ptr TestPackageVersion::GetSource() const + std::shared_ptr TestPackageVersion::GetSource() const { return Source.lock(); } @@ -119,7 +119,7 @@ namespace TestCommon } } - TestPackage::TestPackage(const std::vector& available, std::weak_ptr source) + TestPackage::TestPackage(const std::vector& available, std::weak_ptr source) { for (const auto& manifest : available) { @@ -127,7 +127,7 @@ namespace TestCommon } } - TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available, std::weak_ptr source) : + TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available, std::weak_ptr source) : InstalledVersion(TestPackageVersion::Make(installed, std::move(installationMetadata), source)) { for (const auto& manifest : available) diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h index 510e6ac9f1..3f68d4b40c 100644 --- a/src/AppInstallerCLITests/TestSource.h +++ b/src/AppInstallerCLITests/TestSource.h @@ -18,8 +18,8 @@ namespace TestCommon using LocIndString = AppInstaller::Utility::LocIndString; using MetadataMap = AppInstaller::Repository::IPackageVersion::Metadata; - TestPackageVersion(const Manifest& manifest, std::weak_ptr source = {}); - TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source = {}); + TestPackageVersion(const Manifest& manifest, std::weak_ptr source = {}); + TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source = {}); template static std::shared_ptr Make(Args&&... args) @@ -30,12 +30,12 @@ namespace TestCommon LocIndString GetProperty(AppInstaller::Repository::PackageVersionProperty property) const override; std::vector GetMultiProperty(AppInstaller::Repository::PackageVersionMultiProperty property) const override; Manifest GetManifest() override; - std::shared_ptr GetSource() const override; + std::shared_ptr GetSource() const override; MetadataMap GetMetadata() const override; Manifest VersionManifest; MetadataMap Metadata; - std::weak_ptr Source; + std::weak_ptr Source; protected: static void AddFoldedIfHasValueAndNotPresent(const AppInstaller::Utility::NormalizedString& value, std::vector& target); @@ -50,10 +50,10 @@ namespace TestCommon using MetadataMap = TestPackageVersion::MetadataMap; // Create a package with only available versions using these manifests. - TestPackage(const std::vector& available, std::weak_ptr source = {}); + TestPackage(const std::vector& available, std::weak_ptr source = {}); // Create a package with an installed version, metadata, and optionally available versions. - TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available = {}, std::weak_ptr source = {}); + TestPackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available = {}, std::weak_ptr source = {}); template static std::shared_ptr Make(Args&&... args) diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 2e99614cf3..0cf394d5a9 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -68,7 +68,7 @@ namespace auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector{ manifest }, this->shared_from_this()), + TestPackage::Make(std::vector{ manifest }, const_cast(this)->shared_from_this()), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnOne"))); } else if (input == "TestQueryReturnTwo") @@ -76,13 +76,13 @@ namespace auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector{ manifest }, this->shared_from_this()), + TestPackage::Make(std::vector{ manifest }, const_cast(this)->shared_from_this()), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); auto manifest2 = YamlParser::CreateFromPath(TestDataFile("Manifest-Good.yaml")); result.Matches.emplace_back( ResultMatch( - TestPackage::Make(std::vector{ manifest2 }, this->shared_from_this()), + TestPackage::Make(std::vector{ manifest2 }, const_cast(this)->shared_from_this()), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); } @@ -124,7 +124,7 @@ namespace { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, }, std::vector{ manifest3, manifest2, manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -139,7 +139,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector{ manifest2, manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); } @@ -153,7 +153,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, std::vector{ manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMSStoreInstaller"))); } @@ -168,7 +168,7 @@ namespace manifest2, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, std::vector{ manifest2, manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -183,7 +183,7 @@ namespace manifest, TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, std::vector{ manifest2, manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } @@ -195,7 +195,7 @@ namespace ResultMatch( TestPackage::Make( std::vector{ manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp index f24b6d2b13..3b80e4eb69 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -16,25 +16,25 @@ namespace AppInstaller::Repository::Microsoft // The base for the package objects. struct SourceReference { - SourceReference(const std::shared_ptr& source) : + SourceReference(const std::shared_ptr& source) : m_source(source) {} protected: - std::shared_ptr GetReferenceSource() const + std::shared_ptr GetReferenceSource() const { - std::shared_ptr source = m_source.lock(); + std::shared_ptr source = m_source.lock(); THROW_HR_IF(E_NOT_VALID_STATE, !source); return source; } private: - std::weak_ptr m_source; + std::weak_ptr m_source; }; // The IPackageVersion impl for SQLiteIndexSource. struct PackageVersion : public SourceReference, public IPackageVersion { - PackageVersion(const std::shared_ptr& source, SQLiteIndex::IdType manifestId) : + PackageVersion(const std::shared_ptr& source, SQLiteIndex::IdType manifestId) : SourceReference(source), m_manifestId(manifestId) {} // Inherited via IPackageVersion @@ -67,7 +67,7 @@ namespace AppInstaller::Repository::Microsoft Manifest::Manifest GetManifest() override { - std::shared_ptr source = GetReferenceSource(); + std::shared_ptr source = GetReferenceSource(); std::optional relativePathOpt = source->GetIndex().GetPropertyByManifestId(m_manifestId, PackageVersionProperty::RelativePath); THROW_HR_IF(E_NOT_SET, !relativePathOpt); @@ -82,7 +82,7 @@ namespace AppInstaller::Repository::Microsoft return GetManifestFromArgAndRelativePath(source->GetDetails().Arg, relativePathOpt.value(), manifestSHA256); } - std::shared_ptr GetSource() const override + std::shared_ptr GetSource() const override { return GetReferenceSource(); } @@ -178,7 +178,7 @@ namespace AppInstaller::Repository::Microsoft // The base for IPackage implementations here. struct PackageBase : public SourceReference { - PackageBase(const std::shared_ptr& source, SQLiteIndex::IdType idId) : + PackageBase(const std::shared_ptr& source, SQLiteIndex::IdType idId) : SourceReference(source), m_idId(idId) {} Utility::LocIndString GetProperty(PackageProperty property) const @@ -210,7 +210,7 @@ namespace AppInstaller::Repository::Microsoft protected: std::shared_ptr GetLatestVersionInternal() const { - std::shared_ptr source = GetReferenceSource(); + std::shared_ptr source = GetReferenceSource(); std::optional manifestId = source->GetIndex().GetManifestIdByKey(m_idId, {}, {}); if (manifestId) @@ -242,7 +242,7 @@ namespace AppInstaller::Repository::Microsoft std::vector GetAvailableVersionKeys() const override { - std::shared_ptr source = GetReferenceSource(); + std::shared_ptr source = GetReferenceSource(); std::vector versions = source->GetIndex().GetVersionKeysById(m_idId); std::vector result; @@ -260,7 +260,7 @@ namespace AppInstaller::Repository::Microsoft std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override { - std::shared_ptr source = GetReferenceSource(); + std::shared_ptr source = GetReferenceSource(); // Ensure that this key targets this (or any) source if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) @@ -367,7 +367,7 @@ namespace AppInstaller::Repository::Microsoft auto indexResults = m_index.Search(request); SearchResult result; - std::shared_ptr sharedThis = shared_from_this(); + std::shared_ptr sharedThis = const_cast(this)->shared_from_this(); for (auto& indexResult : indexResults.Matches) { std::unique_ptr package; diff --git a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h index d61aa244e7..59337cec76 100644 --- a/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/AppInstallerRepositorySearch.h @@ -180,7 +180,7 @@ namespace AppInstaller::Repository virtual Manifest::Manifest GetManifest() = 0; // Gets the source where this package version is from. - virtual std::shared_ptr GetSource() const = 0; + virtual std::shared_ptr GetSource() const = 0; // Gets any metadata associated with this package version. // Primarily stores data on installed packages. diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp index e4d6b45eea..ac94cf1b40 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp @@ -14,26 +14,26 @@ namespace AppInstaller::Repository::Rest // The source reference used by package objects. struct SourceReference { - SourceReference(const std::shared_ptr& source) : + SourceReference(const std::shared_ptr& source) : m_source(source) {} protected: - std::shared_ptr GetReferenceSource() const + std::shared_ptr GetReferenceSource() const { - std::shared_ptr source = m_source.lock(); + std::shared_ptr source = m_source.lock(); THROW_HR_IF(E_NOT_VALID_STATE, !source); return source; } private: - std::weak_ptr m_source; + std::weak_ptr m_source; }; // The IPackageVersion impl for RestSource. struct PackageVersion : public SourceReference, public IPackageVersion { PackageVersion( - const std::shared_ptr& source, IRestClient::PackageInfo packageInfo, IRestClient::VersionInfo versionInfo) + const std::shared_ptr& source, IRestClient::PackageInfo packageInfo, IRestClient::VersionInfo versionInfo) : SourceReference(source), m_packageInfo(std::move(packageInfo)), m_versionInfo(std::move(versionInfo)) {} // Inherited via IPackageVersion @@ -132,7 +132,7 @@ namespace AppInstaller::Repository::Rest return m_versionInfo.Manifest.value(); } - std::shared_ptr GetSource() const override + std::shared_ptr GetSource() const override { return GetReferenceSource(); } @@ -169,7 +169,7 @@ namespace AppInstaller::Repository::Rest // The base for IPackage implementations here. struct PackageBase : public SourceReference { - PackageBase(const std::shared_ptr& source, IRestClient::Package&& package) : + PackageBase(const std::shared_ptr& source, IRestClient::Package&& package) : SourceReference(source), m_package(std::move(package)) { // Sort the versions @@ -221,7 +221,7 @@ namespace AppInstaller::Repository::Rest std::vector GetAvailableVersionKeys() const override { - std::shared_ptr source = GetReferenceSource(); + std::shared_ptr source = GetReferenceSource(); std::vector result; for (const auto& versionInfo : m_package.Versions) @@ -240,7 +240,7 @@ namespace AppInstaller::Repository::Rest std::shared_ptr GetAvailableVersion(const PackageVersionKey& versionKey) const override { - std::shared_ptr source = GetReferenceSource(); + std::shared_ptr source = GetReferenceSource(); // Ensure that this key targets this (or any) source if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) @@ -337,7 +337,7 @@ namespace AppInstaller::Repository::Rest RestClient::SearchResult results = m_restClient.Search(request); SearchResult searchResult; - std::shared_ptr sharedThis = shared_from_this(); + std::shared_ptr sharedThis = const_cast(this)->shared_from_this(); for (auto& result : results.Matches) { std::unique_ptr package = std::make_unique(sharedThis, std::move(result)); From 3bac9ab99a834956ac509a4d9f4055971328d286 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 14 Jul 2021 20:19:12 -0300 Subject: [PATCH 16/41] add check for min version --- .../Workflows/DependenciesFlow.cpp | 33 +++++++++++-------- .../Workflows/WorkflowBase.cpp | 9 +++-- .../Workflows/WorkflowBase.h | 1 + .../Public/winget/ManifestCommon.h | 7 +++- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 40648c2dc4..9959e6f977 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -104,9 +104,10 @@ namespace AppInstaller::CLI::Workflow if (context.Contains(Execution::Data::PackageVersion)) { const auto& packageVersion = context.Get(); - // how to execute without progress? should we? - const auto& installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); - auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); + //// how to execute without progress? should we? + //const auto& installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); + //auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); + auto compositeSource = packageVersion->GetSource(); context.Add(compositeSource); } else @@ -120,7 +121,7 @@ namespace AppInstaller::CLI::Workflow void BuildPackageDependenciesGraph(Execution::Context& context) { const auto& rootManifest = context.Get(); - Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id); + Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); std::vector toCheck; std::map> dependencyGraph; //(?) value should be a set instead of a vector? @@ -149,6 +150,7 @@ namespace AppInstaller::CLI::Workflow const auto& source = context.Get(); std::map failedPackages; + std::vector alreadyInstalled; for (int i = 0; i < toCheck.size(); ++i) { @@ -167,33 +169,39 @@ namespace AppInstaller::CLI::Workflow } const auto& package = match.Package; - if (!package->GetInstalledVersion()) + if (package->GetInstalledVersion() && dependencyNode.IsVersionOk(package->GetInstalledVersion()->GetManifest().Version)) + { + alreadyInstalled.push_back(dependencyNode); + } + else { const auto& packageVersion = package->GetLatestAvailableVersion(); if (!packageVersion) { failedPackages[dependencyNode.Id] = "No package version found"; //TODO localize all errors continue; } + const auto& packageVersionManifest = packageVersion->GetManifest(); if (packageVersionManifest.Installers.empty()) { failedPackages[dependencyNode.Id] = "No installers found"; //TODO localize all errors continue; } - - ManifestComparator manifestComparator(context.Args, packageVersion->GetMetadata()); - const auto& matchInstaller = manifestComparator.GetPreferredInstaller(packageVersionManifest); + if (!dependencyNode.IsVersionOk(packageVersionManifest.Version)) + { + failedPackages[dependencyNode.Id] = "Minimum required version not available"; //TODO localize all errors + continue; + } + + const auto& matchInstaller = SelectInstallerFromMetadata(context, packageVersion->GetMetadata()); if (!matchInstaller) { failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors continue; } - //const auto& matchInstaller = packageVersionManifest.Installers.at(0); const auto& matchDependencies = matchInstaller.value().Dependencies; - // TODO check dependency min version is <= latest version - // TODO save installers for later maybe? matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { @@ -207,9 +215,6 @@ namespace AppInstaller::CLI::Workflow } }); } - // TODO else: save information on dependencies already installed to inform the user? - // TODO check dependency min version is <= installed version (otherwise update? -> should check for new dependencies) - } else { diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 5c45b865d7..f0e9ef3a86 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -675,8 +675,13 @@ namespace AppInstaller::CLI::Workflow installationMetadata = context.Get()->GetMetadata(); } - ManifestComparator manifestComparator(context.Args, installationMetadata); - context.Add(manifestComparator.GetPreferredInstaller(context.Get())); + context.Add(SelectInstallerFromMetadata(context, installationMetadata)); + } + + std::optional SelectInstallerFromMetadata(Execution::Context& context, IPackageVersion::Metadata metadata) + { + ManifestComparator manifestComparator(context.Args, metadata); + return manifestComparator.GetPreferredInstaller(context.Get()); } void EnsureRunningAsAdmin(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 1f47e489a6..875f045368 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -288,6 +288,7 @@ namespace AppInstaller::CLI::Workflow // Inputs: Manifest // Outputs: Installer void SelectInstaller(Execution::Context& context); + std::optional SelectInstallerFromMetadata(Execution::Context& context, AppInstaller::Repository::IPackageVersion::Metadata metadata); // Ensures that the process is running as admin. // Required Args: None diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index b64ac42b2f..eb0a1a9cc9 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -40,7 +40,7 @@ namespace AppInstaller::Manifest bool HasExtension() const; - bool HasExtension(std::string_view extension) const; +bool HasExtension(std::string_view extension) const; private: std::vector m_extensions; @@ -138,6 +138,11 @@ namespace AppInstaller::Manifest { return Id < rhs.Id; } + + bool IsVersionOk(string_t version) + { + return MinVersion <= AppInstaller::Utility::Version(version); + } }; struct DependencyList From da52d8152f8b065bef8e01f7813acfaab2ce2e8d Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 14 Jul 2021 21:07:28 -0300 Subject: [PATCH 17/41] spelling --- .../Workflows/DependenciesFlow.cpp | 24 ++++++++++++------- src/AppInstallerCLITests/WorkFlow.cpp | 4 ++-- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 9959e6f977..5ff26c0ba0 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -104,22 +104,24 @@ namespace AppInstaller::CLI::Workflow if (context.Contains(Execution::Data::PackageVersion)) { const auto& packageVersion = context.Get(); - //// how to execute without progress? should we? - //const auto& installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); - //auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); - auto compositeSource = packageVersion->GetSource(); + // how to execute without progress? should we? + const auto& installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); + auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); context.Add(compositeSource); } else { - // TODO question to John: can/should we do nothing for local manifests? or set up something like --dependency-source - // Open source passed by parameter (from sourcename) - // openCompositeSource should work for getting installed+opened source + // TODO set up something like --dependency-source, open source passed by parameter (from source name) + context << + Workflow::OpenSource << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + context.Add(context.Get()); } } void BuildPackageDependenciesGraph(Execution::Context& context) { + auto info = context.Reporter.Info(); const auto& rootManifest = context.Get(); Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); @@ -138,7 +140,12 @@ namespace AppInstaller::CLI::Workflow dependencyGraph[rootDependency].push_back(dependency); }); - } // TODO fail otherwise + } + else + { + info << "no intaller found" << std::endl; + //TODO warn user and raise error, this should not happen as the workflow should fail before reaching this. + } context << OpenDependencySource; if (!context.Contains(Execution::Data::DependencySource)) @@ -222,7 +229,6 @@ namespace AppInstaller::CLI::Workflow continue; } } - auto info = context.Reporter.Info(); auto installationOrder = std::vector(); if (graphHasLoop(dependencyGraph, rootDependency, installationOrder)) { diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 9fac81ddce..fb31cfd983 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -261,7 +261,7 @@ namespace auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Exe_Dependencies.yaml")); manifest.Id = input; manifest.Moniker = input; - // TODO maybe change package name on default locale for better debbugging + // TODO maybe change package name on default locale for better debugging auto& installer = manifest.Installers.at(0); installer.Dependencies.Clear(); @@ -269,7 +269,7 @@ namespace /* * Dependencies: * "A": Depends on the test - * B: NoDeph + * B: NoDependency * C: B * D: E * E: D From 0a8f06938c324a7bf9619b5e6f5aa6005adb143d Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 15 Jul 2021 15:47:54 -0300 Subject: [PATCH 18/41] add dependencies source --- src/AppInstallerCLICore/Commands/InstallCommand.cpp | 1 + src/AppInstallerCLICore/ExecutionArgs.h | 1 + 2 files changed, 2 insertions(+) diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 565ea1a365..2b9ef484a2 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -39,6 +39,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Override), Argument::ForType(Args::Type::InstallLocation), Argument::ForType(Args::Type::HashOverride), + Argument::ForType(Args::Type::DependenciesSource), }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 6e483fb236..fb79336948 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -76,6 +76,7 @@ namespace AppInstaller::CLI::Execution Help, // Show command usage Info, // Show general info about WinGet VerboseLogs, // Increases winget logging level to verbose + DependenciesSource, // Index source to be queried against for finding dependencies // Used for demonstration purposes ExperimentalArg, From d70c55d3f691d3fd662164bb6942e7eda36e0731 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 15 Jul 2021 17:04:39 -0300 Subject: [PATCH 19/41] new parameter dependency source, open source can set up both: source and dependency source --- .../Commands/ExportCommand.cpp | 2 +- .../Commands/ListCommand.cpp | 4 +- .../Commands/SearchCommand.cpp | 4 +- .../Commands/ShowCommand.cpp | 2 +- .../Commands/UninstallCommand.cpp | 4 +- .../Commands/UpgradeCommand.cpp | 4 +- .../ExecutionContextData.h | 2 +- .../Workflows/CompletionFlow.cpp | 2 +- .../Workflows/DependenciesFlow.cpp | 35 ++++------ .../Workflows/WorkflowBase.cpp | 68 ++++++++++++++++--- .../Workflows/WorkflowBase.h | 16 ++++- 11 files changed, 98 insertions(+), 45 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/ExportCommand.cpp b/src/AppInstallerCLICore/Commands/ExportCommand.cpp index 15d0923281..b3e6ff7806 100644 --- a/src/AppInstallerCLICore/Commands/ExportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ExportCommand.cpp @@ -55,7 +55,7 @@ namespace AppInstaller::CLI { context << Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << Workflow::SearchSourceForMany << Workflow::EnsureMatchesFromSearchResult(true) << diff --git a/src/AppInstallerCLICore/Commands/ListCommand.cpp b/src/AppInstallerCLICore/Commands/ListCommand.cpp index 287d75934d..0650a8d869 100644 --- a/src/AppInstallerCLICore/Commands/ListCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ListCommand.cpp @@ -38,7 +38,7 @@ namespace AppInstaller::CLI void ListCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const { context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); switch (valueType) @@ -69,7 +69,7 @@ namespace AppInstaller::CLI void ListCommand::ExecuteInternal(Execution::Context& context) const { context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << Workflow::SearchSourceForMany << Workflow::EnsureMatchesFromSearchResult(true) << diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp index a20c6ec5e0..e688638d65 100644 --- a/src/AppInstallerCLICore/Commands/SearchCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -42,7 +42,7 @@ namespace AppInstaller::CLI { case Execution::Args::Type::Query: context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::RequireCompletionWordNonEmpty << Workflow::SearchSourceForManyCompletion << Workflow::CompleteWithMatchedField; @@ -67,7 +67,7 @@ namespace AppInstaller::CLI void SearchCommand::ExecuteInternal(Context& context) const { context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::SearchSourceForMany << Workflow::EnsureMatchesFromSearchResult(false) << Workflow::ReportSearchResult; diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp index b6aa015b54..e0d9a900d5 100644 --- a/src/AppInstallerCLICore/Commands/ShowCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -61,7 +61,7 @@ namespace AppInstaller::CLI else { context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::SearchSourceForSingle << Workflow::EnsureOneMatchFromSearchResult(false) << Workflow::ReportPackageIdentity << diff --git a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp index 2f9fd4f925..fc7cb2bf44 100644 --- a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp @@ -54,7 +54,7 @@ namespace AppInstaller::CLI } context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); switch (valueType) @@ -104,7 +104,7 @@ namespace AppInstaller::CLI // open the sources where to search for the package context << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); // find the uninstaller diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 7e1b066995..eb281e4b4f 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -68,7 +68,7 @@ namespace AppInstaller::CLI } context << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); switch (valueType) @@ -120,7 +120,7 @@ namespace AppInstaller::CLI context << Workflow::ReportExecutionStage(ExecutionStage::Discovery) << - Workflow::OpenSource << + Workflow::OpenSource() << Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); if (ShouldListUpgrade(context)) diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index 414cb47699..f1c0e63ad1 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -196,7 +196,7 @@ namespace AppInstaller::CLI::Execution template <> struct DataMapping { - using value_t = std::shared_ptr; + using value_t = std::shared_ptr; }; } } diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp index 747960c010..a1e9cf8db2 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -115,7 +115,7 @@ namespace AppInstaller::CLI::Workflow case Execution::Args::Type::Version: case Execution::Args::Type::Channel: context << - Workflow::OpenSource; + Workflow::OpenSource(); break; } diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 5ff26c0ba0..8dc031d6f1 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -92,30 +92,18 @@ namespace AppInstaller::CLI::Workflow void OpenDependencySource(Execution::Context& context) { - // two options: have a package version or a manifest - // try to get source from package version - - //source = empty - //if PackageVersion then source = PV source - //else if Source exists then fail // if Source already open then this can't be an install -m or validate command - //else then OpenSource; source = Source - // result = CreateComposite(installed, source) - if (context.Contains(Execution::Data::PackageVersion)) { const auto& packageVersion = context.Get(); - // how to execute without progress? should we? - const auto& installedSource = context.Reporter.ExecuteWithProgress(std::bind(Repository::OpenPredefinedSource, PredefinedSource::Installed, std::placeholders::_1), true); - auto compositeSource = Repository::CreateCompositeSource(installedSource, packageVersion->GetSource()); - context.Add(compositeSource); + context.Add(packageVersion->GetSource()); + context << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); } else - { - // TODO set up something like --dependency-source, open source passed by parameter (from source name) + { // install from manifest requires --dependency-source to be set context << - Workflow::OpenSource << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - context.Add(context.Get()); + Workflow::OpenSource(true) << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); } } @@ -143,8 +131,15 @@ namespace AppInstaller::CLI::Workflow } else { - info << "no intaller found" << std::endl; - //TODO warn user and raise error, this should not happen as the workflow should fail before reaching this. + info << "no installer found" << std::endl; + //TODO warn user and raise error, this should not happen as the workflow should fail before reaching here. + } + + if (toCheck.empty()) + { + // nothing to do, there's no need to set up dependency source either. + // TODO add information to the logger + return; } context << OpenDependencySource; diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index f0e9ef3a86..eb26d0b519 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -141,12 +141,22 @@ namespace AppInstaller::CLI::Workflow m_func(context); } - void OpenSource(Execution::Context& context) + void OpenSource::operator()(Execution::Context& context) const { std::string_view sourceName; - if (context.Args.Contains(Execution::Args::Type::Source)) + if (m_forDependencies) { - sourceName = context.Args.GetArg(Execution::Args::Type::Source); + if (m_forDependencies && context.Args.Contains(Execution::Args::Type::DependenciesSource)) + { + sourceName = context.Args.GetArg(Execution::Args::Type::DependenciesSource); + } + } + else + { + if (context.Args.Contains(Execution::Args::Type::Source)) + { + sourceName = context.Args.GetArg(Execution::Args::Type::Source); + } } auto source = OpenNamedSource(context, sourceName); @@ -155,7 +165,14 @@ namespace AppInstaller::CLI::Workflow return; } - context.Add(std::move(source)); + if (m_forDependencies) + { + context.Add(std::move(source)); + } + else + { + context.Add(std::move(source)); + } } void OpenNamedSourceForSources::operator()(Execution::Context& context) const @@ -192,22 +209,53 @@ namespace AppInstaller::CLI::Workflow // A well known predefined source should return a value. THROW_HR_IF(E_UNEXPECTED, !source); - context.Add(std::move(source)); + if (m_forDependencies) + { + context.Add(std::move(source)); + } + else + { + context.Add(std::move(source)); + } } void OpenCompositeSource::operator()(Execution::Context& context) const { // Get the already open source for use as the available. - std::shared_ptr availableSource = context.Get(); + std::shared_ptr availableSource; + if (m_forDependencies) + { + availableSource = context.Get(); + } + else + { + availableSource = context.Get(); + } // Open the predefined source. - context << OpenPredefinedSource(m_predefinedSource); + context << OpenPredefinedSource(m_predefinedSource, m_forDependencies); // Create the composite source from the two. - std::shared_ptr compositeSource = Repository::CreateCompositeSource(context.Get(), availableSource); + std::shared_ptr source; + if (m_forDependencies) + { + source = context.Get(); + } + else + { + source = context.Get(); + } + std::shared_ptr compositeSource = Repository::CreateCompositeSource(source, availableSource); // Overwrite the source with the composite. - context.Add(std::move(compositeSource)); + if (m_forDependencies) + { + context.Add(std::move(compositeSource)); + } + else + { + context.Add(std::move(compositeSource)); + } } void SearchSourceForMany(Execution::Context& context) @@ -658,7 +706,7 @@ namespace AppInstaller::CLI::Workflow else { context << - OpenSource << + OpenSource() << SearchSourceForSingle << EnsureOneMatchFromSearchResult(false) << GetManifestFromPackage; diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index 875f045368..b12c0910d9 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -60,7 +60,15 @@ namespace AppInstaller::CLI::Workflow // Required Args: None // Inputs: None // Outputs: Source - void OpenSource(Execution::Context& context); + struct OpenSource : public WorkflowTask + { + OpenSource(bool forDependencies = false) : WorkflowTask("OpenSource"), m_forDependencies(forDependencies) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_forDependencies; + }; // Creates a source object for a source specified by name, and adds it to the list of open sources. // Required Args: None @@ -82,12 +90,13 @@ namespace AppInstaller::CLI::Workflow // Outputs: Source struct OpenPredefinedSource : public WorkflowTask { - OpenPredefinedSource(Repository::PredefinedSource source) : WorkflowTask("OpenPredefinedSource"), m_predefinedSource(source) {} + OpenPredefinedSource(Repository::PredefinedSource source, bool forDependencies = false) : WorkflowTask("OpenPredefinedSource"), m_predefinedSource(source), m_forDependencies(forDependencies) {} void operator()(Execution::Context& context) const override; private: Repository::PredefinedSource m_predefinedSource; + bool m_forDependencies; }; // Creates a composite source from the given predefined source and the existing source. @@ -96,12 +105,13 @@ namespace AppInstaller::CLI::Workflow // Outputs: Source struct OpenCompositeSource : public WorkflowTask { - OpenCompositeSource(Repository::PredefinedSource source) : WorkflowTask("OpenCompositeSource"), m_predefinedSource(source) {} + OpenCompositeSource(Repository::PredefinedSource source, bool forDependencies = false) : WorkflowTask("OpenCompositeSource"), m_predefinedSource(source), m_forDependencies(forDependencies) {} void operator()(Execution::Context& context) const override; private: Repository::PredefinedSource m_predefinedSource; + bool m_forDependencies; }; // Performs a search on the source. From 2e6f29f3a16135cff27edf87d4fbbc7b37142247 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 15 Jul 2021 17:07:51 -0300 Subject: [PATCH 20/41] spelling --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index b93ce7e393..773c58701d 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -32,6 +32,7 @@ badbit Baz Beigi bfd +BFirst bght bitmask bkup From 9d39e48d996e8a9113cd6ccef78c04186f17e5c8 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Fri, 16 Jul 2021 15:09:32 -0300 Subject: [PATCH 21/41] DependencyGraph struct, manages only node/adjacent addition, loop check --- .../Workflows/DependenciesFlow.cpp | 82 ++++------------- .../Workflows/DependenciesFlow.h | 87 +++++++++++++++++-- 2 files changed, 96 insertions(+), 73 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 8dc031d6f1..70c5fdcd7d 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -114,19 +114,17 @@ namespace AppInstaller::CLI::Workflow Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); std::vector toCheck; - std::map> dependencyGraph; //(?) value should be a set instead of a vector? + DependencyGraph dependencyGraph(rootDependency); //(?) value should be a set instead of a vector? const auto& rootInstaller = context.Get(); if (rootInstaller) { context.Add(rootInstaller->Dependencies); // to use in report // TODO remove this ^ if we are reporting dependencies somewhere else while installing/managing them - dependencyGraph[rootDependency] = std::vector(); rootInstaller->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { toCheck.push_back(dependency); - dependencyGraph[dependency] = std::vector(); - dependencyGraph[rootDependency].push_back(dependency); - + dependencyGraph.AddNode(dependency); + dependencyGraph.AddAdjacent(rootDependency, dependency); }); } else @@ -145,9 +143,8 @@ namespace AppInstaller::CLI::Workflow context << OpenDependencySource; if (!context.Contains(Execution::Data::DependencySource)) { - context.Reporter.Info() << "dependency source not found" << std::endl; //TODO localize message - return; //TODO terminate with error once we can get source for dependencies, when a manifest was passed - //AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); // TODO create a new error code? + info << "dependency source not found" << std::endl; //TODO localize message + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); // TODO create a new error code? } const auto& source = context.Get(); @@ -177,25 +174,25 @@ namespace AppInstaller::CLI::Workflow } else { - const auto& packageVersion = package->GetLatestAvailableVersion(); - if (!packageVersion) { + const auto& packageLatestVersion = package->GetLatestAvailableVersion(); + if (!packageLatestVersion) { failedPackages[dependencyNode.Id] = "No package version found"; //TODO localize all errors continue; } - const auto& packageVersionManifest = packageVersion->GetManifest(); - if (packageVersionManifest.Installers.empty()) { + const auto& packageLatestVersionManifest = packageLatestVersion->GetManifest(); + if (packageLatestVersionManifest.Installers.empty()) { failedPackages[dependencyNode.Id] = "No installers found"; //TODO localize all errors continue; } - if (!dependencyNode.IsVersionOk(packageVersionManifest.Version)) + if (!dependencyNode.IsVersionOk(packageLatestVersionManifest.Version)) { failedPackages[dependencyNode.Id] = "Minimum required version not available"; //TODO localize all errors continue; } - const auto& matchInstaller = SelectInstallerFromMetadata(context, packageVersion->GetMetadata()); + const auto& matchInstaller = SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()); if (!matchInstaller) { failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors @@ -207,13 +204,12 @@ namespace AppInstaller::CLI::Workflow // TODO save installers for later maybe? matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { - dependencyGraph[dependencyNode].push_back(dependency); + dependencyGraph.AddAdjacent(dependencyNode, dependency); - auto search = dependencyGraph.find(dependency); - if (search == dependencyGraph.end()) // if not found + if (!dependencyGraph.HasNode(dependency)) { toCheck.push_back(dependency); - dependencyGraph[dependency] = std::vector(); + dependencyGraph.AddNode(dependency); } }); } @@ -224,8 +220,7 @@ namespace AppInstaller::CLI::Workflow continue; } } - auto installationOrder = std::vector(); - if (graphHasLoop(dependencyGraph, rootDependency, installationOrder)) + if (dependencyGraph.HasLoop()) { info << "has loop" << std::endl; //TODO warn user and raise error @@ -233,51 +228,6 @@ namespace AppInstaller::CLI::Workflow // TODO raise error for failedPackages (if there's at least one) - info << "order: "; - for (auto const& node : installationOrder) - { - info << node.Id << ", "; - } - info << std::endl; - } - - // TODO make them iterative - // is there a better way that this to check for loops? - bool graphHasLoop(const std::map>& dependencyGraph, const Dependency& root, std::vector& order) - { - auto visited = std::set(); - visited.insert(root); - if (hasLoopDFS(visited, root, dependencyGraph, order)) - { - return true; - } - return false; - } - - bool hasLoopDFS(std::set visited, const Dependency& node, const std::map>& dependencyGraph, std::vector& order) - { - visited.insert(node); - for (const auto& adjacent : dependencyGraph.at(node)) - { - auto search = visited.find(adjacent); - if (search == visited.end()) // if not found - { - if (hasLoopDFS(visited, adjacent, dependencyGraph, order)) - { - return true; - } - } - else - { - return true; - } - } - - if (std::find(order.begin(), order.end(), node) == order.end()) - { - order.push_back(node); - } - - return false; + dependencyGraph.PrintOrder(info); } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index abc66f7d0b..1a0c4137a4 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -51,11 +51,84 @@ namespace AppInstaller::CLI::Workflow // Outputs: DependencySource void OpenDependencySource(Execution::Context& context); - bool graphHasLoop(const std::map>& dependencyGraph, - const AppInstaller::Manifest::Dependency& root, - std::vector& order); - bool hasLoopDFS(std::set visited, - const AppInstaller::Manifest::Dependency& node, - const std::map>& dependencyGraph, - std::vector& order); + struct DependencyGraph + { + DependencyGraph(AppInstaller::Manifest::Dependency root) : m_root(root) + { + adjacents[m_root] = std::vector(); + + } + + void AddNode(AppInstaller::Manifest::Dependency node) + { + adjacents[node] = std::vector(); + + } + + void AddAdjacent(AppInstaller::Manifest::Dependency node, AppInstaller::Manifest::Dependency adjacent) + { + adjacents[node].push_back(adjacent); + } + + bool HasNode(AppInstaller::Manifest::Dependency dependency) + { + auto search = adjacents.find(dependency); + return search == adjacents.end(); + } + + // TODO make HasLoop and HasLoopDFS iterative + bool HasLoop() + { + auto visited = std::set(); + visited.insert(m_root); + if (HasLoopDFS(visited, m_root)) + { + return true; + } + return false; + } + + //-- only for debugging + void PrintOrder(AppInstaller::CLI::Execution::OutputStream info) + { + info << "order: "; + for (auto const& node : installationOrder) + { + info << node.Id << ", "; + } + info << std::endl; + } + + private: + bool HasLoopDFS(std::set visited, const AppInstaller::Manifest::Dependency& node) + { + visited.insert(node); + for (const auto& adjacent : adjacents.at(node)) + { + auto search = visited.find(adjacent); + if (search == visited.end()) // if not found + { + if (HasLoopDFS(visited, adjacent)) + { + return true; + } + } + else + { + return true; + } + } + + if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) + { + installationOrder.push_back(node); + } + + return false; + } + + AppInstaller::Manifest::Dependency m_root; + std::map> adjacents; + std::vector installationOrder; + }; } \ No newline at end of file From 00489c6f0d345a59d4a66a3ae1d64e2feb0daa49 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Fri, 16 Jul 2021 15:09:49 -0300 Subject: [PATCH 22/41] OpenSource() missing --- src/AppInstallerCLITests/WorkFlow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index fb31cfd983..1c1dbcb9f3 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -434,7 +434,7 @@ namespace void OverrideForOpenSource(TestContext& context) { - context.Override({ Workflow::OpenSource, [](TestContext& context) + context.Override({ Workflow::OpenSource(), [](TestContext& context) { context.Add(std::make_shared()); } }); @@ -442,7 +442,7 @@ void OverrideForOpenSource(TestContext& context) void OverrideForCompositeInstalledSource(TestContext& context) { - context.Override({ Workflow::OpenSource, [](TestContext&) + context.Override({ Workflow::OpenSource(), [](TestContext&) { } }); @@ -467,7 +467,7 @@ void OverrideForImportSource(TestContext& context) void OverrideForDependencySource(TestContext& context) { - context.Override({ Workflow::OpenSource, [](TestContext& context) + context.Override({ Workflow::OpenSource(), [](TestContext& context) { context.Add(std::make_shared()); } }); From 572c2bf2a5b361a0bd4e70c6b6a0963bfad0afb7 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Fri, 16 Jul 2021 15:13:55 -0300 Subject: [PATCH 23/41] add adjacents to spellchecker --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 773c58701d..1544ff27b2 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -1,4 +1,5 @@ abcd +adjacents adml admx affle From 70978eebf75fe5b88487bb5adcd74a642a7cf4c3 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 19 Jul 2021 15:52:45 -0300 Subject: [PATCH 24/41] graph logic is ok, installer selection and test source settings needs to be fixed --- .../Workflows/DependenciesFlow.cpp | 28 +++++++++++-------- .../Workflows/DependenciesFlow.h | 6 ++-- src/AppInstallerCLITests/WorkFlow.cpp | 14 +++++----- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 70c5fdcd7d..23f1966039 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -96,8 +96,8 @@ namespace AppInstaller::CLI::Workflow { const auto& packageVersion = context.Get(); context.Add(packageVersion->GetSource()); - context << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); + /*context << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true);*/ } else { // install from manifest requires --dependency-source to be set @@ -192,14 +192,18 @@ namespace AppInstaller::CLI::Workflow continue; } - const auto& matchInstaller = SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()); - if (!matchInstaller) - { - failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors - continue; - } - - const auto& matchDependencies = matchInstaller.value().Dependencies; + // TODO FIX THIS, have a better way to pick installer (other than the first one) + // the problem is SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()) uses context data so it ends up returning + // the installer for the root package being installed. + //const auto& matchInstaller = SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()); + //if (!matchInstaller) + //{ + // failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors + // continue; + //} + + //const auto& matchDependencies = matchInstaller.value().Dependencies; + const auto& matchDependencies = packageLatestVersionManifest.Installers.at(0).Dependencies; // TODO save installers for later maybe? matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) @@ -223,7 +227,9 @@ namespace AppInstaller::CLI::Workflow if (dependencyGraph.HasLoop()) { info << "has loop" << std::endl; - //TODO warn user and raise error + Logging::Log().Write(Logging::Channel::CLI, Logging::Level::Warning, "Dependency loop found"); //TODO localization + //TODO warn user but try to install either way + return; } // TODO raise error for failedPackages (if there's at least one) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 1a0c4137a4..036c1d14bf 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -73,14 +73,14 @@ namespace AppInstaller::CLI::Workflow bool HasNode(AppInstaller::Manifest::Dependency dependency) { auto search = adjacents.find(dependency); - return search == adjacents.end(); + return search != adjacents.end(); } // TODO make HasLoop and HasLoopDFS iterative bool HasLoop() { - auto visited = std::set(); - visited.insert(m_root); + installationOrder.clear(); + std::set visited; if (HasLoopDFS(visited, m_root)) { return true; diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 1c1dbcb9f3..af4b505a88 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -216,7 +216,7 @@ namespace { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, }, std::vector{ manifest2, manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller.Dependencies"))); } @@ -228,7 +228,7 @@ namespace ResultMatch( TestPackage::Make( std::vector{ manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller.WFDep"))); } @@ -334,7 +334,7 @@ namespace ResultMatch( TestPackage::Make( std::vector{ manifest }, - this->shared_from_this() + const_cast(this)->shared_from_this() ), PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); return result; @@ -434,7 +434,7 @@ namespace void OverrideForOpenSource(TestContext& context) { - context.Override({ Workflow::OpenSource(), [](TestContext& context) + context.Override({ "OpenSource", [](TestContext& context) { context.Add(std::make_shared()); } }); @@ -442,7 +442,7 @@ void OverrideForOpenSource(TestContext& context) void OverrideForCompositeInstalledSource(TestContext& context) { - context.Override({ Workflow::OpenSource(), [](TestContext&) + context.Override({ "OpenSource", [](TestContext&) { } }); @@ -467,7 +467,7 @@ void OverrideForImportSource(TestContext& context) void OverrideForDependencySource(TestContext& context) { - context.Override({ Workflow::OpenSource(), [](TestContext& context) + context.Override({ "OpenSource", [](TestContext& context) { context.Add(std::make_shared()); } }); @@ -1804,7 +1804,7 @@ TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGr INFO(installOutput.str()); REQUIRE(installOutput.str().find("has loop") == std::string::npos); - REQUIRE(installOutput.str().find("B, C, F, DependencyAlreadyInStackButNoLoop,") != std::string::npos); + REQUIRE(installOutput.str().find("order: B, C, F, DependencyAlreadyInStackButNoLoop,") != std::string::npos); } TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph]") From 743ca96f37010590f1004eed175f2f0d309af8bf Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 19 Jul 2021 16:55:00 -0300 Subject: [PATCH 25/41] dependency-source argument --- src/AppInstallerCLICore/Argument.cpp | 2 ++ src/AppInstallerCLICore/Commands/InstallCommand.cpp | 2 +- src/AppInstallerCLICore/ExecutionArgs.h | 2 +- src/AppInstallerCLICore/Resources.h | 1 + src/AppInstallerCLICore/Workflows/WorkflowBase.cpp | 4 ++-- src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 4 ++++ 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index fb672e57a2..28fd6e0301 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -31,6 +31,8 @@ namespace AppInstaller::CLI return Argument{ "command", NoAlias, Args::Type::Command, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; case Args::Type::Source: return Argument{ "source", 's', Args::Type::Source, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; + case Args::Type::DependencySource: + return Argument{ "dependency-source", NoAlias, Args::Type::DependencySource, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; case Args::Type::Count: return Argument{ "count", 'n', Args::Type::Count, Resource::String::CountArgumentDescription, ArgumentType::Standard }; case Args::Type::Exact: diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 2b9ef484a2..0f7222406a 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -39,7 +39,7 @@ namespace AppInstaller::CLI Argument::ForType(Args::Type::Override), Argument::ForType(Args::Type::InstallLocation), Argument::ForType(Args::Type::HashOverride), - Argument::ForType(Args::Type::DependenciesSource), + Argument::ForType(Args::Type::DependencySource), }; } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index fb79336948..ad7794f00c 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -76,7 +76,7 @@ namespace AppInstaller::CLI::Execution Help, // Show command usage Info, // Show general info about WinGet VerboseLogs, // Increases winget logging level to verbose - DependenciesSource, // Index source to be queried against for finding dependencies + DependencySource, // Index source to be queried against for finding dependencies // Used for demonstration purposes ExperimentalArg, diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index fc992bc902..ed4208b70f 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -191,6 +191,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependencySourceArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandLongDescription); diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index eb26d0b519..bd34327981 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -146,9 +146,9 @@ namespace AppInstaller::CLI::Workflow std::string_view sourceName; if (m_forDependencies) { - if (m_forDependencies && context.Args.Contains(Execution::Args::Type::DependenciesSource)) + if (m_forDependencies && context.Args.Contains(Execution::Args::Type::DependencySource)) { - sourceName = context.Args.GetArg(Execution::Args::Type::DependenciesSource); + sourceName = context.Args.GetArg(Execution::Args::Type::DependencySource); } } else diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 9e00e20f10..e10ff60bcb 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -949,4 +949,8 @@ Configuration is disabled due to Group Policy. Windows Libraries + + Find package depedencies using the specified source + For getting package type dependencies when installing from a local manifest + \ No newline at end of file From edde7c0976f3f0cf219541937aa8633cc296ae46 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 19 Jul 2021 18:34:02 -0300 Subject: [PATCH 26/41] typo --- src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index e10ff60bcb..b5588a4ae6 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -950,7 +950,7 @@ Configuration is disabled due to Group Policy. Windows Libraries - Find package depedencies using the specified source + Find package dependencies using the specified source For getting package type dependencies when installing from a local manifest \ No newline at end of file From 25f30decb0d3717421de2c198ac673120eeca292 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 21 Jul 2021 12:33:38 -0300 Subject: [PATCH 27/41] DependencyGraph receives function to search for dependencies, auto-builds the graph --- .../Workflows/DependenciesFlow.cpp | 158 ++++++++---------- .../Workflows/DependenciesFlow.h | 66 +++++++- .../Public/winget/ManifestCommon.h | 25 +-- 3 files changed, 140 insertions(+), 109 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 23f1966039..c3c53cdae1 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -111,32 +111,16 @@ namespace AppInstaller::CLI::Workflow { auto info = context.Reporter.Info(); const auto& rootManifest = context.Get(); - Dependency rootDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); + Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); - std::vector toCheck; - DependencyGraph dependencyGraph(rootDependency); //(?) value should be a set instead of a vector? - const auto& rootInstaller = context.Get(); - if (rootInstaller) - { - context.Add(rootInstaller->Dependencies); // to use in report - // TODO remove this ^ if we are reporting dependencies somewhere else while installing/managing them - rootInstaller->Dependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) - { - toCheck.push_back(dependency); - dependencyGraph.AddNode(dependency); - dependencyGraph.AddAdjacent(rootDependency, dependency); - }); - } - else - { - info << "no installer found" << std::endl; - //TODO warn user and raise error, this should not happen as the workflow should fail before reaching here. - } - - if (toCheck.empty()) + const auto& rootDependencies = context.Get()->Dependencies; + // installer should exist, otherwise previous workflows should have failed + context.Add(rootDependencies); // to use in report + // TODO remove this ^ if we are reporting dependencies somewhere else while installing/managing them + + if (rootDependencies.Empty()) { - // nothing to do, there's no need to set up dependency source either. - // TODO add information to the logger + // If there's no dependencies there's nothing to do aside of logging the outcome return; } @@ -148,82 +132,76 @@ namespace AppInstaller::CLI::Workflow } const auto& source = context.Get(); - std::map failedPackages; - std::vector alreadyInstalled; - for (int i = 0; i < toCheck.size(); ++i) - { - auto dependencyNode = toCheck.at(i); + DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, + [&](Dependency node) { + auto info = context.Reporter.Info(); - SearchRequest searchRequest; - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, dependencyNode.Id)); - const auto& matches = source->Search(searchRequest).Matches; + SearchRequest searchRequest; + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, node.Id)); + //TODO add min version filter to search request ? + const auto& matches = source->Search(searchRequest).Matches; - if (!matches.empty()) - { - const auto& match = matches.at(0); - if (matches.size() > 1) { - failedPackages[dependencyNode.Id] = "Too many matches"; //TODO localize all errors - continue; - } - - const auto& package = match.Package; - if (package->GetInstalledVersion() && dependencyNode.IsVersionOk(package->GetInstalledVersion()->GetManifest().Version)) - { - alreadyInstalled.push_back(dependencyNode); - } - else + if (!matches.empty()) { - const auto& packageLatestVersion = package->GetLatestAvailableVersion(); - if (!packageLatestVersion) { - failedPackages[dependencyNode.Id] = "No package version found"; //TODO localize all errors - continue; - } - - const auto& packageLatestVersionManifest = packageLatestVersion->GetManifest(); - if (packageLatestVersionManifest.Installers.empty()) { - failedPackages[dependencyNode.Id] = "No installers found"; //TODO localize all errors - continue; + if (matches.size() > 1) { + info << "Too many matches"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors } + const auto& match = matches.at(0); - if (!dependencyNode.IsVersionOk(packageLatestVersionManifest.Version)) + const auto& package = match.Package; + if (package->GetInstalledVersion() && node.IsVersionOk(package->GetInstalledVersion()->GetManifest().Version)) { - failedPackages[dependencyNode.Id] = "Minimum required version not available"; //TODO localize all errors - continue; + return DependencyList(); //return empty dependency list, as we won't keep searching for dependencies for installed packages + //TODO we should have this information on the graph, to avoid trying to install it later } - - // TODO FIX THIS, have a better way to pick installer (other than the first one) - // the problem is SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()) uses context data so it ends up returning - // the installer for the root package being installed. - //const auto& matchInstaller = SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()); - //if (!matchInstaller) - //{ - // failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors - // continue; - //} - - //const auto& matchDependencies = matchInstaller.value().Dependencies; - const auto& matchDependencies = packageLatestVersionManifest.Installers.at(0).Dependencies; - - // TODO save installers for later maybe? - matchDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + else + { + const auto& packageLatestVersion = package->GetLatestAvailableVersion(); + if (!packageLatestVersion) { + info << "No package version found"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + + const auto& packageLatestVersionManifest = packageLatestVersion->GetManifest(); + if (packageLatestVersionManifest.Installers.empty()) { + info << "No installers found"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + + if (!node.IsVersionOk(packageLatestVersionManifest.Version)) { - dependencyGraph.AddAdjacent(dependencyNode, dependency); - - if (!dependencyGraph.HasNode(dependency)) - { - toCheck.push_back(dependency); - dependencyGraph.AddNode(dependency); - } - }); + info << "Minimum required version not available"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + + // TODO FIX THIS, have a better way to pick installer (other than the first one) + // the problem is SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()) uses context data so it ends up returning + // the installer for the root package being installed. + //const auto& matchInstaller = SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()); + //if (!matchInstaller) + //{ + // failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors + // continue; + //} + // TODO save installers for later maybe? + + //const auto& matchDependencies = matchInstaller.value().Dependencies; + const auto& matchDependencies = packageLatestVersionManifest.Installers.at(0).Dependencies; + + return matchDependencies; + } } - } - else - { - failedPackages[dependencyNode.Id] = "No matches"; //TODO localize all errors - continue; - } - } + else + { + info << "No matches"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + }); + + dependencyGraph.BuildGraph(); // maybe it's better if it already does it on the constructor? + if (dependencyGraph.HasLoop()) { info << "has loop" << std::endl; diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 036c1d14bf..91a62281ab 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -53,10 +53,46 @@ namespace AppInstaller::CLI::Workflow struct DependencyGraph { - DependencyGraph(AppInstaller::Manifest::Dependency root) : m_root(root) + DependencyGraph(AppInstaller::Manifest::Dependency root, + AppInstaller::Manifest::DependencyList rootDependencies, + std::function infoFunction) : m_root(root), getDependencies(infoFunction) { adjacents[m_root] = std::vector(); + toCheck = std::vector(); + rootDependencies.ApplyToType(AppInstaller::Manifest::DependencyType::Package, [&](AppInstaller::Manifest::Dependency dependency) + { + toCheck.push_back(dependency); + AddNode(dependency); + AddAdjacent(root, dependency); + }); + } + + void BuildGraph() + { + if (toCheck.empty()) + { + return; + } + + for (int i = 0; i < toCheck.size(); ++i) + { + auto node = toCheck.at(i); + + const auto& nodeDependencies = getDependencies(node); //TODO add error stream so we can report back + + nodeDependencies.ApplyToType(AppInstaller::Manifest::DependencyType::Package, [&](AppInstaller::Manifest::Dependency dependency) + { + if (!HasNode(dependency)) + { + toCheck.push_back(dependency); + AddNode(dependency); + } + + AddAdjacent(node, dependency); + }); + } + CheckForLoopsAndGetOrder(); } void AddNode(AppInstaller::Manifest::Dependency node) @@ -76,16 +112,22 @@ namespace AppInstaller::CLI::Workflow return search != adjacents.end(); } - // TODO make HasLoop and HasLoopDFS iterative bool HasLoop() { - installationOrder.clear(); + return hasLoop; + } + + // TODO make CheckForLoops and HasLoopDFS iterative + void CheckForLoopsAndGetOrder() + { + installationOrder = std::vector(); std::set visited; - if (HasLoopDFS(visited, m_root)) - { - return true; - } - return false; + hasLoop = HasLoopDFS(visited, m_root); + } + + std::vector GetInstallationOrder() + { + return installationOrder; } //-- only for debugging @@ -128,7 +170,13 @@ namespace AppInstaller::CLI::Workflow } AppInstaller::Manifest::Dependency m_root; - std::map> adjacents; + std::map> adjacents; //(?) value should be a set instead of a vector? + std::function getDependencies; + + bool hasLoop; std::vector installationOrder; + + std::vector toCheck; + std::map failedPackages; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index eb0a1a9cc9..99c218c74b 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -204,6 +204,21 @@ bool HasExtension(std::string_view extension) const; return nullptr; } + void ApplyToType(DependencyType type, std::function func) const + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type) func(dependency); + } + } + + bool Empty() const + { + return dependencies.empty(); + } + + void Clear() { dependencies.clear(); } + // for testing purposes bool HasExactDependency(DependencyType type, string_t id, string_t minVersion = "") { @@ -230,16 +245,6 @@ bool HasExtension(std::string_view extension) const; return dependencies.size(); } - void ApplyToType(DependencyType type, std::function func) const - { - for (const auto& dependency : dependencies) - { - if (dependency.Type == type) func(dependency); - } - } - - void Clear() { dependencies.clear(); } - private: std::vector dependencies; }; From ef3e6a0b0a1a09a621cd945a67dc97d9c617a231 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 22 Jul 2021 13:00:02 -0300 Subject: [PATCH 28/41] dependency table, create with id --- .../Workflows/DependenciesFlow.h | 2 +- .../Public/winget/ManifestCommon.h | 8 +++++++ .../AppInstallerRepositoryCore.vcxproj | 1 + ...AppInstallerRepositoryCore.vcxproj.filters | 3 +++ .../Schema/1_3/DependenciesTable.cpp | 0 .../Microsoft/Schema/1_3/DependenciesTable.h | 22 +++++++++++++++++++ .../Microsoft/Schema/1_3/Interface_1_3.cpp | 17 ++++++++++++++ 7 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.cpp create mode 100644 src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.h diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 91a62281ab..279efbcece 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -117,7 +117,6 @@ namespace AppInstaller::CLI::Workflow return hasLoop; } - // TODO make CheckForLoops and HasLoopDFS iterative void CheckForLoopsAndGetOrder() { installationOrder = std::vector(); @@ -142,6 +141,7 @@ namespace AppInstaller::CLI::Workflow } private: + // TODO make HasLoopDFS iterative bool HasLoopDFS(std::set visited, const AppInstaller::Manifest::Dependency& node) { visited.insert(node); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 99c218c74b..dbf0359c5c 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -212,6 +212,14 @@ bool HasExtension(std::string_view extension) const; } } + void ApplyToAll(std::function func) const + { + for (const auto& dependency : dependencies) + { + func(dependency); + } + } + bool Empty() const { return dependencies.empty(); diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index de08205e68..4807c40d57 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -225,6 +225,7 @@ + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index 11971b5953..ab6ac072a0 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -216,6 +216,9 @@ Microsoft\Schema\1_3 + + Microsoft\Schema\1_3 + diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.h new file mode 100644 index 0000000000..74f1234538 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/DependenciesTable.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_3 +{ + namespace details + { + using namespace std::string_view_literals; + + struct DependenciesTableInfo + { + inline static constexpr std::string_view TableName() { return "dependencies"sv; } + inline static constexpr std::string_view ValueName() { return "id"sv; } + }; + } + + // The table for Dependencies. + using DependenciesTable = V1_0::OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp index 68f258ff5e..16bb32d2a1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp @@ -8,9 +8,22 @@ #include "Microsoft/Schema/1_3/HashVirtualTable.h" +#include "Microsoft/Schema/1_3/DependenciesTable.h" + namespace AppInstaller::Repository::Microsoft::Schema::V1_3 { + std::vector> GetDependencies(const Manifest::Manifest& manifest) + { + std::vector> manifestDependencies; + + manifest.DefaultInstallerInfo.Dependencies.ApplyToAll([&](AppInstaller::Manifest::Dependency dependency) + { + manifestDependencies.push_back(dependency.Id); + }); + return manifestDependencies; + } + Interface::Interface(Utility::NormalizationVersion normVersion) : V1_2::Interface(normVersion) { } @@ -28,6 +41,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_3 V1_0::ManifestTable::AddColumn(connection, { HashVirtualTable::ValueName(), HashVirtualTable::SQLiteType() }); + DependenciesTable::Create(connection); + savepoint.Commit(); } @@ -44,6 +59,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_3 V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, manifest.StreamSha256); } + DependenciesTable::EnsureExistsAndInsert(connection, GetDependencies(manifest), manifestId); + savepoint.Commit(); return manifestId; From e371668988b98c2e0d836d838481248423d37adb Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Fri, 23 Jul 2021 13:55:34 -0300 Subject: [PATCH 29/41] dependency graph logic moved to manifest common --- .../Workflows/DependenciesFlow.cpp | 9 +- .../Workflows/DependenciesFlow.h | 129 ------------------ .../Public/winget/ManifestCommon.h | 119 ++++++++++++++++ 3 files changed, 127 insertions(+), 130 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index c3c53cdae1..a432258bb3 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -212,6 +212,13 @@ namespace AppInstaller::CLI::Workflow // TODO raise error for failedPackages (if there's at least one) - dependencyGraph.PrintOrder(info); + //-- only for debugging + const auto& installationOrder = dependencyGraph.GetInstallationOrder(); + info << "order: "; + for (auto const& node : installationOrder) + { + info << node.Id << ", "; + } + info << std::endl; } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 279efbcece..67eb8b70f1 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -50,133 +50,4 @@ namespace AppInstaller::CLI::Workflow // Inputs: PackageVersion, Manifest // Outputs: DependencySource void OpenDependencySource(Execution::Context& context); - - struct DependencyGraph - { - DependencyGraph(AppInstaller::Manifest::Dependency root, - AppInstaller::Manifest::DependencyList rootDependencies, - std::function infoFunction) : m_root(root), getDependencies(infoFunction) - { - adjacents[m_root] = std::vector(); - toCheck = std::vector(); - rootDependencies.ApplyToType(AppInstaller::Manifest::DependencyType::Package, [&](AppInstaller::Manifest::Dependency dependency) - { - toCheck.push_back(dependency); - AddNode(dependency); - AddAdjacent(root, dependency); - }); - } - - void BuildGraph() - { - if (toCheck.empty()) - { - return; - } - - for (int i = 0; i < toCheck.size(); ++i) - { - auto node = toCheck.at(i); - - const auto& nodeDependencies = getDependencies(node); //TODO add error stream so we can report back - - nodeDependencies.ApplyToType(AppInstaller::Manifest::DependencyType::Package, [&](AppInstaller::Manifest::Dependency dependency) - { - if (!HasNode(dependency)) - { - toCheck.push_back(dependency); - AddNode(dependency); - } - - AddAdjacent(node, dependency); - }); - } - - CheckForLoopsAndGetOrder(); - } - - void AddNode(AppInstaller::Manifest::Dependency node) - { - adjacents[node] = std::vector(); - - } - - void AddAdjacent(AppInstaller::Manifest::Dependency node, AppInstaller::Manifest::Dependency adjacent) - { - adjacents[node].push_back(adjacent); - } - - bool HasNode(AppInstaller::Manifest::Dependency dependency) - { - auto search = adjacents.find(dependency); - return search != adjacents.end(); - } - - bool HasLoop() - { - return hasLoop; - } - - void CheckForLoopsAndGetOrder() - { - installationOrder = std::vector(); - std::set visited; - hasLoop = HasLoopDFS(visited, m_root); - } - - std::vector GetInstallationOrder() - { - return installationOrder; - } - - //-- only for debugging - void PrintOrder(AppInstaller::CLI::Execution::OutputStream info) - { - info << "order: "; - for (auto const& node : installationOrder) - { - info << node.Id << ", "; - } - info << std::endl; - } - - private: - // TODO make HasLoopDFS iterative - bool HasLoopDFS(std::set visited, const AppInstaller::Manifest::Dependency& node) - { - visited.insert(node); - for (const auto& adjacent : adjacents.at(node)) - { - auto search = visited.find(adjacent); - if (search == visited.end()) // if not found - { - if (HasLoopDFS(visited, adjacent)) - { - return true; - } - } - else - { - return true; - } - } - - if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) - { - installationOrder.push_back(node); - } - - return false; - } - - AppInstaller::Manifest::Dependency m_root; - std::map> adjacents; //(?) value should be a set instead of a vector? - std::function getDependencies; - - bool hasLoop; - std::vector installationOrder; - - std::vector toCheck; - std::map failedPackages; - }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index dbf0359c5c..c726c19285 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace AppInstaller::Manifest @@ -257,6 +258,124 @@ bool HasExtension(std::string_view extension) const; std::vector dependencies; }; + struct DependencyGraph + { + DependencyGraph(Dependency root, DependencyList rootDependencies, + std::function infoFunction) : m_root(root), getDependencies(infoFunction) + { + adjacents[m_root] = std::vector(); + toCheck = std::vector(); + rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + toCheck.push_back(dependency); + AddNode(dependency); + AddAdjacent(root, dependency); + }); + } + + void BuildGraph() + { + if (toCheck.empty()) + { + return; + } + + for (int i = 0; i < toCheck.size(); ++i) + { + auto node = toCheck.at(i); + + const auto& nodeDependencies = getDependencies(node); + //TODO add error stream so we can report back + + nodeDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + if (!HasNode(dependency)) + { + toCheck.push_back(dependency); + AddNode(dependency); + } + + AddAdjacent(node, dependency); + }); + } + + CheckForLoopsAndGetOrder(); + } + + void AddNode(Dependency node) + { + adjacents[node] = std::vector(); + + } + + void AddAdjacent(Dependency node, Dependency adjacent) + { + adjacents[node].push_back(adjacent); + } + + bool HasNode(Dependency dependency) + { + auto search = adjacents.find(dependency); + return search != adjacents.end(); + } + + bool HasLoop() + { + return hasLoop; + } + + void CheckForLoopsAndGetOrder() + { + installationOrder = std::vector(); + std::set visited; + hasLoop = HasLoopDFS(visited, m_root); + } + + std::vector GetInstallationOrder() + { + return installationOrder; + } + + private: + // TODO make this function iterative + bool HasLoopDFS(std::set visited, const Dependency& node) + { + visited.insert(node); + for (const auto& adjacent : adjacents.at(node)) + { + auto search = visited.find(adjacent); + if (search == visited.end()) // if not found + { + if (HasLoopDFS(visited, adjacent)) + { + return true; + } + } + else + { + return true; + } + } + + if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) + { + installationOrder.push_back(node); + } + + return false; + } + + Dependency m_root; + std::map> adjacents; //(?) value should be a set instead of a vector? + std::function getDependencies; + + bool hasLoop; + std::vector installationOrder; + + std::vector toCheck; + std::map failedPackages; + }; + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); From e6c00d62b3055ee63a0125904bb949219198f670 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Mon, 26 Jul 2021 18:16:06 -0300 Subject: [PATCH 30/41] refactor --- .../Workflows/DependenciesFlow.cpp | 57 ++++++++++++------- .../Workflows/InstallFlow.cpp | 21 +++++-- .../Workflows/InstallFlow.h | 4 ++ src/AppInstallerCLITests/WorkFlow.cpp | 4 ++ .../Public/winget/ManifestCommon.h | 16 +++++- 5 files changed, 73 insertions(+), 29 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index a432258bb3..1917a5d931 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -3,8 +3,10 @@ #include "pch.h" #include "DependenciesFlow.h" +#include "InstallFlow.h" #include "ManifestComparator.h" + namespace AppInstaller::CLI::Workflow { using namespace AppInstaller::Repository; @@ -107,6 +109,7 @@ namespace AppInstaller::CLI::Workflow } } + void BuildPackageDependenciesGraph(Execution::Context& context) { auto info = context.Reporter.Info(); @@ -128,10 +131,11 @@ namespace AppInstaller::CLI::Workflow if (!context.Contains(Execution::Data::DependencySource)) { info << "dependency source not found" << std::endl; //TODO localize message - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); // TODO create a new error code? + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); // TODO create specific error code } const auto& source = context.Get(); + std::map dependenciesInstallers; DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, [&](Dependency node) { @@ -155,42 +159,40 @@ namespace AppInstaller::CLI::Workflow { return DependencyList(); //return empty dependency list, as we won't keep searching for dependencies for installed packages //TODO we should have this information on the graph, to avoid trying to install it later + // TODO if it's already installed we need to upgrade it } else { - const auto& packageLatestVersion = package->GetLatestAvailableVersion(); - if (!packageLatestVersion) { + const auto& latestVersion = package->GetLatestAvailableVersion(); + if (!latestVersion) { info << "No package version found"; //TODO localize all errors return DependencyList(); //return empty dependency list, TODO change this to actually manage errors } - const auto& packageLatestVersionManifest = packageLatestVersion->GetManifest(); - if (packageLatestVersionManifest.Installers.empty()) { + const auto& manifest = latestVersion->GetManifest(); + if (manifest.Installers.empty()) { info << "No installers found"; //TODO localize all errors return DependencyList(); //return empty dependency list, TODO change this to actually manage errors } - if (!node.IsVersionOk(packageLatestVersionManifest.Version)) + if (!node.IsVersionOk(manifest.Version)) { info << "Minimum required version not available"; //TODO localize all errors return DependencyList(); //return empty dependency list, TODO change this to actually manage errors } // TODO FIX THIS, have a better way to pick installer (other than the first one) + const auto* installer = &manifest.Installers.at(0); // the problem is SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()) uses context data so it ends up returning // the installer for the root package being installed. - //const auto& matchInstaller = SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()); - //if (!matchInstaller) - //{ - // failedPackages[dependencyNode.Id] = "No installer found"; //TODO localize all errors - // continue; - //} - // TODO save installers for later maybe? - - //const auto& matchDependencies = matchInstaller.value().Dependencies; - const auto& matchDependencies = packageLatestVersionManifest.Installers.at(0).Dependencies; - - return matchDependencies; + //const auto& installer = SelectInstallerFromMetadata(context, latestVersion->GetMetadata()); + + const auto& nodeDependencies = installer->Dependencies; + + //auto packageDescription = AppInstaller::CLI::PackageCollection::Package(manifest.Id, manifest.Version, manifest.Channel); + // create package description too be able to use it for installer + //dependenciesInstallers[node.Id] = PackagesAndInstallers(installer, latestVersion, manifest.Version, manifest.Channel); + return nodeDependencies; } } else @@ -212,13 +214,24 @@ namespace AppInstaller::CLI::Workflow // TODO raise error for failedPackages (if there's at least one) - //-- only for debugging const auto& installationOrder = dependencyGraph.GetInstallationOrder(); - info << "order: "; + + + std::vector installers; + + info << "order: "; //-- only for debugging for (auto const& node : installationOrder) { - info << node.Id << ", "; + info << node.Id << ", "; //-- only for debugging + installers.push_back(dependenciesInstallers.find(node.Id)->second); + } + info << std::endl; //-- only for debugging + + bool allSucceeded = InstallPackages(context, installers); + if (!allSucceeded) + { + context.Reporter.Error() << "error installing dependencies" << std::endl; //TODO localize error + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); // TODO create specific error code } - info << std::endl; } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 1be6c27851..9dffc8d0d0 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -466,6 +466,19 @@ namespace AppInstaller::CLI::Workflow context << Workflow::ReportDependencies(Resource::String::ImportCommandReportDependencies); } + allSucceeded &= InstallPackages(context, installers); + + if (!allSucceeded) + { + context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED); + } + } + + bool InstallPackages(Execution::Context& context, std::vector installers) + { + bool allSucceeded = true; + for (auto packageAndInstaller : installers) { auto package = packageAndInstaller.Package; @@ -490,18 +503,14 @@ namespace AppInstaller::CLI::Workflow { // This means that the subcontext being terminated is due to an overall abort context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; + return false; } allSucceeded = false; } } - if (!allSucceeded) - { - context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED); - } + return allSucceeded; } void SnapshotARPEntries(Execution::Context& context) try diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index e21cdee97a..10e1e15943 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -121,4 +121,8 @@ namespace AppInstaller::CLI::Workflow std::optional Installer; AppInstaller::CLI::Execution::PackageToInstall Package; }; + + // Installs packages and returns if all succeeded or not + bool InstallPackages(Execution::Context& context, std::vector installers); + } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index af4b505a88..1071471523 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -330,6 +330,10 @@ namespace installer.Dependencies.Add(Dependency(DependencyType::Package, "H")); } + //TODO: + // test for installed packages (or the ones that need upgrade) + // test for different min Version of dependencies + result.Matches.emplace_back( ResultMatch( TestPackage::Make( diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index c726c19285..098b9da152 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -260,6 +260,7 @@ bool HasExtension(std::string_view extension) const; struct DependencyGraph { + // this constructor was intented for use during installation flow (we already have installer dependencies and there's no need to search the source again) DependencyGraph(Dependency root, DependencyList rootDependencies, std::function infoFunction) : m_root(root), getDependencies(infoFunction) { @@ -273,6 +274,20 @@ bool HasExtension(std::string_view extension) const; }); } + DependencyGraph(Dependency root, std::function infoFunction) : m_root(root), getDependencies(infoFunction) + { + adjacents[m_root] = std::vector(); + toCheck = std::vector(); + + DependencyList rootDependencies = getDependencies(root); + rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + toCheck.push_back(dependency); + AddNode(dependency); + AddAdjacent(root, dependency); + }); + } + void BuildGraph() { if (toCheck.empty()) @@ -305,7 +320,6 @@ bool HasExtension(std::string_view extension) const; void AddNode(Dependency node) { adjacents[node] = std::vector(); - } void AddAdjacent(Dependency node, Dependency adjacent) From 7b4238a98d3f0cec28b36aff3ebe81e7b8f857fd Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Tue, 27 Jul 2021 16:41:00 -0300 Subject: [PATCH 31/41] break InstallMultiple workflow into two --- .../Commands/ImportCommand.cpp | 2 +- .../ExecutionContextData.h | 14 ++++ .../Workflows/DependenciesFlow.cpp | 68 ++++++++++--------- .../Workflows/InstallFlow.cpp | 41 ++++++----- .../Workflows/InstallFlow.h | 21 ++---- .../Workflows/WorkflowBase.cpp | 4 +- 6 files changed, 82 insertions(+), 68 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/ImportCommand.cpp b/src/AppInstallerCLICore/Commands/ImportCommand.cpp index 7d7c9c2a19..1f6922c5c2 100644 --- a/src/AppInstallerCLICore/Commands/ImportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ImportCommand.cpp @@ -47,6 +47,6 @@ namespace AppInstaller::CLI Workflow::OpenPredefinedSource(Repository::PredefinedSource::Installed) << Workflow::SearchPackagesForImport << Workflow::ReportExecutionStage(Workflow::ExecutionStage::Execution) << - Workflow::InstallMultiple; + Workflow::SelectInstallerMultiple; } } diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index f1c0e63ad1..78e554d405 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -44,6 +44,7 @@ namespace AppInstaller::CLI::Execution PackageCollection, // On import: A collection of specific package versions to install PackagesToInstall, + InstallersToInstall, // On import: Sources for the imported packages Sources, ARPSnapshot, @@ -58,6 +59,13 @@ namespace AppInstaller::CLI::Execution PackageCollection::Package PackageRequest; }; + struct InstallerToInstall + { + std::shared_ptr PackageVersion; + Manifest::ManifestInstaller Installer; + bool IsUpdate = false; + }; + namespace details { template @@ -173,6 +181,12 @@ namespace AppInstaller::CLI::Execution { using value_t = std::vector; }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; template <> struct DataMapping diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 1917a5d931..31cd0487f2 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -3,8 +3,8 @@ #include "pch.h" #include "DependenciesFlow.h" -#include "InstallFlow.h" #include "ManifestComparator.h" +#include "InstallFlow.h" namespace AppInstaller::CLI::Workflow @@ -98,8 +98,8 @@ namespace AppInstaller::CLI::Workflow { const auto& packageVersion = context.Get(); context.Add(packageVersion->GetSource()); - /*context << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true);*/ + context << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); } else { // install from manifest requires --dependency-source to be set @@ -114,12 +114,13 @@ namespace AppInstaller::CLI::Workflow { auto info = context.Reporter.Info(); const auto& rootManifest = context.Get(); + Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); - const auto& rootDependencies = context.Get()->Dependencies; - // installer should exist, otherwise previous workflows should have failed - context.Add(rootDependencies); // to use in report - // TODO remove this ^ if we are reporting dependencies somewhere else while installing/managing them + const auto& rootInstaller = context.Get(); + const auto& rootDependencies = rootInstaller->Dependencies; + + context.Add(rootDependencies); //information needed to report dependencies if (rootDependencies.Empty()) { @@ -135,7 +136,10 @@ namespace AppInstaller::CLI::Workflow } const auto& source = context.Get(); - std::map dependenciesInstallers; + std::map idToInstallerMap; + + //idToInstallerMap[rootManifest.Id] = {rootVersion, rootInstaller.value(), false}; + // Question: where do I get the root version from? DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, [&](Dependency node) { @@ -158,8 +162,6 @@ namespace AppInstaller::CLI::Workflow if (package->GetInstalledVersion() && node.IsVersionOk(package->GetInstalledVersion()->GetManifest().Version)) { return DependencyList(); //return empty dependency list, as we won't keep searching for dependencies for installed packages - //TODO we should have this information on the graph, to avoid trying to install it later - // TODO if it's already installed we need to upgrade it } else { @@ -171,7 +173,7 @@ namespace AppInstaller::CLI::Workflow const auto& manifest = latestVersion->GetManifest(); if (manifest.Installers.empty()) { - info << "No installers found"; //TODO localize all errors + info << "No installers found"; //TODO localize all errors return DependencyList(); //return empty dependency list, TODO change this to actually manage errors } @@ -181,17 +183,21 @@ namespace AppInstaller::CLI::Workflow return DependencyList(); //return empty dependency list, TODO change this to actually manage errors } - // TODO FIX THIS, have a better way to pick installer (other than the first one) - const auto* installer = &manifest.Installers.at(0); - // the problem is SelectInstallerFromMetadata(context, packageLatestVersion->GetMetadata()) uses context data so it ends up returning - // the installer for the root package being installed. - //const auto& installer = SelectInstallerFromMetadata(context, latestVersion->GetMetadata()); + std::optional installer; + bool isUpdate = false; + if (package->GetInstalledVersion()) + { + installer = SelectInstallerFromMetadata(context, package->GetInstalledVersion()->GetMetadata()); + isUpdate = true; + } + else + { + installer = SelectInstallerFromMetadata(context, {}); + } const auto& nodeDependencies = installer->Dependencies; - //auto packageDescription = AppInstaller::CLI::PackageCollection::Package(manifest.Id, manifest.Version, manifest.Channel); - // create package description too be able to use it for installer - //dependenciesInstallers[node.Id] = PackagesAndInstallers(installer, latestVersion, manifest.Version, manifest.Channel); + idToInstallerMap[node.Id] = {latestVersion, installer.value(), isUpdate}; return nodeDependencies; } } @@ -209,29 +215,29 @@ namespace AppInstaller::CLI::Workflow info << "has loop" << std::endl; Logging::Log().Write(Logging::Channel::CLI, Logging::Level::Warning, "Dependency loop found"); //TODO localization //TODO warn user but try to install either way - return; } - // TODO raise error for failedPackages (if there's at least one) + // TODO raise error for failed packages? (if there's at least one) const auto& installationOrder = dependencyGraph.GetInstallationOrder(); - - std::vector installers; + std::vector installers; info << "order: "; //-- only for debugging for (auto const& node : installationOrder) { info << node.Id << ", "; //-- only for debugging - installers.push_back(dependenciesInstallers.find(node.Id)->second); + + auto itr = idToInstallerMap.find(node.Id); + // if the package was already installed (with a useful version) there will be no installer for it on the map. + if (itr != idToInstallerMap.end()) + { + installers.push_back(itr->second); + } } info << std::endl; //-- only for debugging - - bool allSucceeded = InstallPackages(context, installers); - if (!allSucceeded) - { - context.Reporter.Error() << "error installing dependencies" << std::endl; //TODO localize error - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); // TODO create specific error code - } + + context.Add(installers); + context << InstallMultiple; } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 9dffc8d0d0..625a9499d7 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -7,7 +7,7 @@ #include "ShellExecuteInstallerHandler.h" #include "MSStoreInstallerHandler.h" #include "WorkflowBase.h" -#include "Workflows/DependenciesFlow.h" +#include "DependenciesFlow.h" namespace AppInstaller::CLI::Workflow { @@ -416,15 +416,15 @@ namespace AppInstaller::CLI::Workflow Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << Workflow::BuildPackageDependenciesGraph << - Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies)<< Workflow::InstallPackageInstaller; } - void InstallMultiple(Execution::Context& context) + void SelectInstallerMultiple(Execution::Context& context) { bool allSucceeded = true; DependencyList allDependencies; - std::vector installers; + std::vector installers; for (auto package : context.Get()) { @@ -452,11 +452,11 @@ namespace AppInstaller::CLI::Workflow } const auto& installer = installContext.Get(); - installers.push_back(PackagesAndInstallers(installer, package)); + installers.push_back({package.PackageVersion, installer.value(), false}); if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) { - if (installer) allDependencies.Add(installer->Dependencies); + allDependencies.Add(installer->Dependencies); } } @@ -465,32 +465,27 @@ namespace AppInstaller::CLI::Workflow context.Add(allDependencies); context << Workflow::ReportDependencies(Resource::String::ImportCommandReportDependencies); } - - allSucceeded &= InstallPackages(context, installers); - - if (!allSucceeded) - { - context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED); - } + context.Add(installers); + context << InstallMultiple; } - bool InstallPackages(Execution::Context& context, std::vector installers) + void InstallMultiple(Execution::Context& context) { bool allSucceeded = true; + const auto& installers = context.Get(); + for (auto packageAndInstaller : installers) { - auto package = packageAndInstaller.Package; + auto packageVersion = packageAndInstaller.PackageVersion; auto installer = packageAndInstaller.Installer; auto installContextPtr = context.Clone(); Execution::Context& installContext = *installContextPtr; // set data needed for installing - installContext.Add(package.PackageVersion); - installContext.Add(package.PackageVersion->GetManifest()); - installContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(package.PackageRequest.Scope)); + installContext.Add(packageVersion); + installContext.Add(packageVersion->GetManifest()); installContext.Add(installer); installContext << @@ -503,14 +498,18 @@ namespace AppInstaller::CLI::Workflow { // This means that the subcontext being terminated is due to an overall abort context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return false; + return; } allSucceeded = false; } } - return allSucceeded; + if (!allSucceeded) + { + context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED); + } } void SnapshotARPEntries(Execution::Context& context) try diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 10e1e15943..157568689c 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -95,9 +95,15 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void InstallPackageVersion(Execution::Context& context); + // Selects the installer for each of the packages to install. + // Required Args: None + // Inputs: PackagesToInstall + // Outputs: InstallersToInstall + void SelectInstallerMultiple(Execution::Context& context); + // Installs multiple packages. // Required Args: None - // Inputs: Manifests + // Inputs: InstallersToInstall // Outputs: None void InstallMultiple(Execution::Context& context); @@ -112,17 +118,4 @@ namespace AppInstaller::CLI::Workflow // Inputs: ARPSnapshot?, Manifest, PackageVersion // Outputs: None void ReportARPChanges(Execution::Context& context); - - const struct PackagesAndInstallers - { - PackagesAndInstallers(std::optional inst, - AppInstaller::CLI::Execution::PackageToInstall pkg) : Installer(inst), Package(pkg) {} - - std::optional Installer; - AppInstaller::CLI::Execution::PackageToInstall Package; - }; - - // Installs packages and returns if all succeeded or not - bool InstallPackages(Execution::Context& context, std::vector installers); - } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index bd34327981..12fe687a7d 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -237,15 +237,17 @@ namespace AppInstaller::CLI::Workflow // Create the composite source from the two. std::shared_ptr source; + std::shared_ptr compositeSource; if (m_forDependencies) { source = context.Get(); + compositeSource = Repository::CreateCompositeSource(source, availableSource, CompositeSearchBehavior::AvailablePackages); } else { source = context.Get(); + compositeSource = Repository::CreateCompositeSource(source, availableSource); } - std::shared_ptr compositeSource = Repository::CreateCompositeSource(source, availableSource); // Overwrite the source with the composite. if (m_forDependencies) From a05f384b5585b722e19b790beac380b349bf8276 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 28 Jul 2021 11:41:05 -0300 Subject: [PATCH 32/41] SelectInstallerFromMetadata does not receive context --- .../Workflows/DependenciesFlow.cpp | 19 +++++++++++-------- .../Workflows/DependenciesFlow.h | 2 +- .../Workflows/InstallFlow.cpp | 4 ++-- .../Workflows/WorkflowBase.cpp | 8 ++++---- .../Workflows/WorkflowBase.h | 2 +- .../Public/winget/ManifestCommon.h | 18 +++++++++--------- 6 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 31cd0487f2..ba497b3929 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -110,7 +110,7 @@ namespace AppInstaller::CLI::Workflow } - void BuildPackageDependenciesGraph(Execution::Context& context) + void ManagePackageDependencies(Execution::Context& context) { auto info = context.Reporter.Info(); const auto& rootManifest = context.Get(); @@ -138,9 +138,6 @@ namespace AppInstaller::CLI::Workflow const auto& source = context.Get(); std::map idToInstallerMap; - //idToInstallerMap[rootManifest.Id] = {rootVersion, rootInstaller.value(), false}; - // Question: where do I get the root version from? - DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, [&](Dependency node) { auto info = context.Reporter.Info(); @@ -187,12 +184,12 @@ namespace AppInstaller::CLI::Workflow bool isUpdate = false; if (package->GetInstalledVersion()) { - installer = SelectInstallerFromMetadata(context, package->GetInstalledVersion()->GetMetadata()); + installer = SelectInstallerFromMetadata(context.Args, manifest, package->GetInstalledVersion()->GetMetadata()); isUpdate = true; } else { - installer = SelectInstallerFromMetadata(context, {}); + installer = SelectInstallerFromMetadata(context.Args, manifest, {}); } const auto& nodeDependencies = installer->Dependencies; @@ -214,7 +211,8 @@ namespace AppInstaller::CLI::Workflow { info << "has loop" << std::endl; Logging::Log().Write(Logging::Channel::CLI, Logging::Level::Warning, "Dependency loop found"); //TODO localization - //TODO warn user but try to install either way + //TODO warn user but try to install either way (right now packages are only added to installation order if there's not a loop) + return; } // TODO raise error for failed packages? (if there's at least one) @@ -229,7 +227,9 @@ namespace AppInstaller::CLI::Workflow info << node.Id << ", "; //-- only for debugging auto itr = idToInstallerMap.find(node.Id); - // if the package was already installed (with a useful version) there will be no installer for it on the map. + // if the package was already installed (with a useful version) + // or is the root + // then there will be no installer for it on the map. if (itr != idToInstallerMap.end()) { installers.push_back(itr->second); @@ -237,7 +237,10 @@ namespace AppInstaller::CLI::Workflow } info << std::endl; //-- only for debugging + // Install dependencies in the correct order context.Add(installers); context << InstallMultiple; + + // Install the root (continue) } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 67eb8b70f1..b97ab50995 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -43,7 +43,7 @@ namespace AppInstaller::CLI::Workflow // Required Args: None // Inputs: DependencySource // Outputs: Dependencies - void BuildPackageDependenciesGraph(Execution::Context& context); + void ManagePackageDependencies(Execution::Context& context); // Sets up the source used to get the dependencies. // Required Args: None diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 625a9499d7..0cfd4302c4 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -415,8 +415,8 @@ namespace AppInstaller::CLI::Workflow Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << - Workflow::BuildPackageDependenciesGraph << - Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies)<< + Workflow::ManagePackageDependencies << + Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::InstallPackageInstaller; } diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index 12fe687a7d..f4109ad169 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -725,13 +725,13 @@ namespace AppInstaller::CLI::Workflow installationMetadata = context.Get()->GetMetadata(); } - context.Add(SelectInstallerFromMetadata(context, installationMetadata)); + context.Add(SelectInstallerFromMetadata(context.Args, context.Get(), installationMetadata)); } - std::optional SelectInstallerFromMetadata(Execution::Context& context, IPackageVersion::Metadata metadata) + std::optional SelectInstallerFromMetadata(Execution::Args args, AppInstaller::Manifest::Manifest manifest, AppInstaller::Repository::IPackageVersion::Metadata metadata) { - ManifestComparator manifestComparator(context.Args, metadata); - return manifestComparator.GetPreferredInstaller(context.Get()); + ManifestComparator manifestComparator(args, metadata); + return manifestComparator.GetPreferredInstaller(manifest); } void EnsureRunningAsAdmin(Execution::Context& context) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index b12c0910d9..1faf376664 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -298,7 +298,7 @@ namespace AppInstaller::CLI::Workflow // Inputs: Manifest // Outputs: Installer void SelectInstaller(Execution::Context& context); - std::optional SelectInstallerFromMetadata(Execution::Context& context, AppInstaller::Repository::IPackageVersion::Metadata metadata); + std::optional SelectInstallerFromMetadata(Execution::Args args, AppInstaller::Manifest::Manifest manifest, AppInstaller::Repository::IPackageVersion::Metadata metadata); // Ensures that the process is running as admin. // Required Args: None diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 098b9da152..b6147acc2a 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -261,8 +261,8 @@ bool HasExtension(std::string_view extension) const; struct DependencyGraph { // this constructor was intented for use during installation flow (we already have installer dependencies and there's no need to search the source again) - DependencyGraph(Dependency root, DependencyList rootDependencies, - std::function infoFunction) : m_root(root), getDependencies(infoFunction) + DependencyGraph(const Dependency& root, const DependencyList& rootDependencies, + std::function infoFunction) : m_root(root), getDependencies(infoFunction) { adjacents[m_root] = std::vector(); toCheck = std::vector(); @@ -274,12 +274,12 @@ bool HasExtension(std::string_view extension) const; }); } - DependencyGraph(Dependency root, std::function infoFunction) : m_root(root), getDependencies(infoFunction) + DependencyGraph(const Dependency& root, std::function infoFunction) : m_root(root), getDependencies(infoFunction) { adjacents[m_root] = std::vector(); toCheck = std::vector(); - DependencyList rootDependencies = getDependencies(root); + const DependencyList& rootDependencies = getDependencies(root); rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) { toCheck.push_back(dependency); @@ -317,17 +317,17 @@ bool HasExtension(std::string_view extension) const; CheckForLoopsAndGetOrder(); } - void AddNode(Dependency node) + void AddNode(const Dependency& node) { adjacents[node] = std::vector(); } - void AddAdjacent(Dependency node, Dependency adjacent) + void AddAdjacent(const Dependency& node,const Dependency& adjacent) { adjacents[node].push_back(adjacent); } - bool HasNode(Dependency dependency) + bool HasNode(const Dependency& dependency) { auto search = adjacents.find(dependency); return search != adjacents.end(); @@ -379,9 +379,9 @@ bool HasExtension(std::string_view extension) const; return false; } - Dependency m_root; + const Dependency& m_root; std::map> adjacents; //(?) value should be a set instead of a vector? - std::function getDependencies; + std::function getDependencies; bool hasLoop; std::vector installationOrder; From 4a1aec8c8b66177c78226c462c5cf65d0fd62f2e Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 28 Jul 2021 14:02:22 -0300 Subject: [PATCH 33/41] leaving workflow to work as in report dependencies, with the addition of managing package dep --- .../Workflows/DependenciesFlow.cpp | 8 ++------ src/AppInstallerCLICore/Workflows/InstallFlow.cpp | 1 + src/AppInstallerCLITests/WorkFlow.cpp | 10 +++++----- .../Public/winget/ManifestCommon.h | 11 ++++++----- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index ba497b3929..22bcb0b28f 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -120,8 +120,6 @@ namespace AppInstaller::CLI::Workflow const auto& rootInstaller = context.Get(); const auto& rootDependencies = rootInstaller->Dependencies; - context.Add(rootDependencies); //information needed to report dependencies - if (rootDependencies.Empty()) { // If there's no dependencies there's nothing to do aside of logging the outcome @@ -211,8 +209,7 @@ namespace AppInstaller::CLI::Workflow { info << "has loop" << std::endl; Logging::Log().Write(Logging::Channel::CLI, Logging::Level::Warning, "Dependency loop found"); //TODO localization - //TODO warn user but try to install either way (right now packages are only added to installation order if there's not a loop) - return; + //warn user but try to install either way } // TODO raise error for failed packages? (if there's at least one) @@ -227,8 +224,7 @@ namespace AppInstaller::CLI::Workflow info << node.Id << ", "; //-- only for debugging auto itr = idToInstallerMap.find(node.Id); - // if the package was already installed (with a useful version) - // or is the root + // if the package was already installed (with a useful version) or is the root // then there will be no installer for it on the map. if (itr != idToInstallerMap.end()) { diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 0cfd4302c4..13b1425119 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -415,6 +415,7 @@ namespace AppInstaller::CLI::Workflow Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << + Workflow::GetDependenciesFromInstaller << Workflow::ManagePackageDependencies << Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::InstallPackageInstaller; diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 1071471523..f6961a7536 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -1768,7 +1768,7 @@ TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); } -TEST_CASE("DependencyGraph_Loop", "[InstallFlow][workflow][dependencyGraph]") +TEST_CASE("DependencyGraph_Loop", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1789,7 +1789,7 @@ TEST_CASE("DependencyGraph_Loop", "[InstallFlow][workflow][dependencyGraph]") REQUIRE(installOutput.str().find("has loop") != std::string::npos); } -TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGraph]") +TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1811,7 +1811,7 @@ TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGr REQUIRE(installOutput.str().find("order: B, C, F, DependencyAlreadyInStackButNoLoop,") != std::string::npos); } -TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph]") +TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1834,7 +1834,7 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph } -TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph]") +TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1857,7 +1857,7 @@ TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyG } -TEST_CASE("DependencyGraph_BFirst", "[InstallFlow][workflow][dependencyGraph]") +TEST_CASE("DependencyGraph_BFirst", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index b6147acc2a..3173c444f2 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -354,6 +354,12 @@ bool HasExtension(std::string_view extension) const; // TODO make this function iterative bool HasLoopDFS(std::set visited, const Dependency& node) { + // Adding before checking for loops, to have an order even if a loop is present + if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) + { + installationOrder.push_back(node); + } + visited.insert(node); for (const auto& adjacent : adjacents.at(node)) { @@ -371,11 +377,6 @@ bool HasExtension(std::string_view extension) const; } } - if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) - { - installationOrder.push_back(node); - } - return false; } From 29bc5fde4f208221bc2c9f1c1091d24e9748b4fa Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Wed, 28 Jul 2021 15:47:11 -0300 Subject: [PATCH 34/41] move dependencies to context data --- .../Workflows/DependenciesFlow.cpp | 2 +- .../Workflows/InstallFlow.cpp | 2 +- .../Public/winget/ManifestCommon.h | 6 +- .../DependenciesFlow.cpp | 242 ++++++ .../ManifestCommon.h | 421 ++++++++++ .../InstallFlow.cpp | 731 ++++++++++++++++++ 6 files changed, 1399 insertions(+), 5 deletions(-) create mode 100644 src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp create mode 100644 src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h create mode 100644 src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 22bcb0b28f..070672eb61 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -67,7 +67,7 @@ namespace AppInstaller::CLI::Workflow allDependencies.Add(installer.Dependencies); } - context.Add(allDependencies); + context.Add(std::move(allDependencies)); } } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 13b1425119..15f462ab07 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -463,7 +463,7 @@ namespace AppInstaller::CLI::Workflow if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) { - context.Add(allDependencies); + context.Add(std::move(allDependencies)); context << Workflow::ReportDependencies(Resource::String::ImportCommandReportDependencies); } context.Add(installers); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 3173c444f2..83bcde2eca 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -128,7 +128,7 @@ bool HasExtension(std::string_view extension) const; std::optional MinVersion; Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(AppInstaller::Utility::Version(minVersion)) {} - Dependency(DependencyType type, string_t id) : Type(std::move(type)), Id(std::move(id)) {} + Dependency(DependencyType type, string_t id) : Type(type), Id(std::move(id)) {} Dependency(DependencyType type) : Type(type) {} bool operator==(const Dependency& rhs) const { @@ -152,7 +152,7 @@ bool HasExtension(std::string_view extension) const; void Add(const Dependency& newDependency) { - Dependency* existingDependency = this->HasDependency(newDependency); + Dependency* existingDependency = HasDependency(newDependency); if (existingDependency != NULL) { if (newDependency.MinVersion) @@ -180,7 +180,7 @@ bool HasExtension(std::string_view extension) const; { for (const auto& dependency : otherDependencyList.dependencies) { - this->Add(dependency); + Add(dependency); } } diff --git a/src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp b/src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp new file mode 100644 index 0000000000..22bcb0b28f --- /dev/null +++ b/src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp @@ -0,0 +1,242 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "DependenciesFlow.h" +#include "ManifestComparator.h" +#include "InstallFlow.h" + + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::Repository; + using namespace Manifest; + + void ReportDependencies::operator()(Execution::Context& context) const + { + if (!Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + return; + } + auto info = context.Reporter.Info(); + + const auto& dependencies = context.Get(); + if (dependencies.HasAny()) + { + info << Resource::StringId(m_messageId) << std::endl; + + if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) + { + info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; + dependencies.ApplyToType(DependencyType::WindowsFeature, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + + if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) + { + info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; + dependencies.ApplyToType(DependencyType::WindowsLibrary, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + + if (dependencies.HasAnyOf(DependencyType::Package)) + { + info << " - " << Resource::String::PackageDependencies << std::endl; + dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) + { + info << " " << dependency.Id; + if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value().ToString() << "]"; + info << std::endl; + }); + } + + if (dependencies.HasAnyOf(DependencyType::External)) + { + info << " - " << Resource::String::ExternalDependencies << std::endl; + dependencies.ApplyToType(DependencyType::External, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + } + } + + void GetInstallersDependenciesFromManifest(Execution::Context& context) { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + const auto& manifest = context.Get(); + DependencyList allDependencies; + + for (const auto& installer : manifest.Installers) + { + allDependencies.Add(installer.Dependencies); + } + + context.Add(allDependencies); + } + } + + void GetDependenciesFromInstaller(Execution::Context& context) + { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + const auto& installer = context.Get(); + if (installer) + { + context.Add(installer->Dependencies); + } + } + } + + void GetDependenciesInfoForUninstall(Execution::Context& context) + { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + // 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 + context.Add(DependencyList()); // sending empty list of dependencies for now + } + } + + void OpenDependencySource(Execution::Context& context) + { + if (context.Contains(Execution::Data::PackageVersion)) + { + const auto& packageVersion = context.Get(); + context.Add(packageVersion->GetSource()); + context << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); + } + else + { // install from manifest requires --dependency-source to be set + context << + Workflow::OpenSource(true) << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); + } + } + + + void ManagePackageDependencies(Execution::Context& context) + { + auto info = context.Reporter.Info(); + const auto& rootManifest = context.Get(); + + Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); + + const auto& rootInstaller = context.Get(); + const auto& rootDependencies = rootInstaller->Dependencies; + + if (rootDependencies.Empty()) + { + // If there's no dependencies there's nothing to do aside of logging the outcome + return; + } + + context << OpenDependencySource; + if (!context.Contains(Execution::Data::DependencySource)) + { + info << "dependency source not found" << std::endl; //TODO localize message + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); // TODO create specific error code + } + + const auto& source = context.Get(); + std::map idToInstallerMap; + + DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, + [&](Dependency node) { + auto info = context.Reporter.Info(); + + SearchRequest searchRequest; + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, node.Id)); + //TODO add min version filter to search request ? + const auto& matches = source->Search(searchRequest).Matches; + + if (!matches.empty()) + { + if (matches.size() > 1) { + info << "Too many matches"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + const auto& match = matches.at(0); + + const auto& package = match.Package; + if (package->GetInstalledVersion() && node.IsVersionOk(package->GetInstalledVersion()->GetManifest().Version)) + { + return DependencyList(); //return empty dependency list, as we won't keep searching for dependencies for installed packages + } + else + { + const auto& latestVersion = package->GetLatestAvailableVersion(); + if (!latestVersion) { + info << "No package version found"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + + const auto& manifest = latestVersion->GetManifest(); + if (manifest.Installers.empty()) { + info << "No installers found"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + + if (!node.IsVersionOk(manifest.Version)) + { + info << "Minimum required version not available"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + + std::optional installer; + bool isUpdate = false; + if (package->GetInstalledVersion()) + { + installer = SelectInstallerFromMetadata(context.Args, manifest, package->GetInstalledVersion()->GetMetadata()); + isUpdate = true; + } + else + { + installer = SelectInstallerFromMetadata(context.Args, manifest, {}); + } + + const auto& nodeDependencies = installer->Dependencies; + + idToInstallerMap[node.Id] = {latestVersion, installer.value(), isUpdate}; + return nodeDependencies; + } + } + else + { + info << "No matches"; //TODO localize all errors + return DependencyList(); //return empty dependency list, TODO change this to actually manage errors + } + }); + + dependencyGraph.BuildGraph(); // maybe it's better if it already does it on the constructor? + + if (dependencyGraph.HasLoop()) + { + info << "has loop" << std::endl; + Logging::Log().Write(Logging::Channel::CLI, Logging::Level::Warning, "Dependency loop found"); //TODO localization + //warn user but try to install either way + } + + // TODO raise error for failed packages? (if there's at least one) + + const auto& installationOrder = dependencyGraph.GetInstallationOrder(); + + std::vector installers; + + info << "order: "; //-- only for debugging + for (auto const& node : installationOrder) + { + info << node.Id << ", "; //-- only for debugging + + auto itr = idToInstallerMap.find(node.Id); + // if the package was already installed (with a useful version) or is the root + // then there will be no installer for it on the map. + if (itr != idToInstallerMap.end()) + { + installers.push_back(itr->second); + } + } + info << std::endl; //-- only for debugging + + // Install dependencies in the correct order + context.Add(installers); + context << InstallMultiple; + + // Install the root (continue) + } +} \ No newline at end of file diff --git a/src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h b/src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h new file mode 100644 index 0000000000..3173c444f2 --- /dev/null +++ b/src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h @@ -0,0 +1,421 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Manifest +{ + using string_t = Utility::NormalizedString; + using namespace std::string_view_literals; + + // The maximum supported major version known about by this code. + constexpr uint64_t s_MaxSupportedMajorVersion = 1; + + // The default manifest version assigned to manifests without a ManifestVersion field. + constexpr std::string_view s_DefaultManifestVersion = "0.1.0"sv; + + // V1 manifest version for GA + constexpr std::string_view s_ManifestVersionV1 = "1.0.0"sv; + + // The manifest extension for the MS Store + constexpr std::string_view s_MSStoreExtension = "msstore"sv; + + // ManifestVer is inherited from Utility::Version and is a more restricted version. + // ManifestVer is used to specify the version of app manifest itself. + // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] + // and optionally a following tag in the format of -[SomeString] for experimental purpose. + struct ManifestVer : public Utility::Version + { + ManifestVer() = default; + + ManifestVer(std::string_view version); + + uint64_t Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } + uint64_t Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } + uint64_t Patch() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } + + bool HasExtension() const; + +bool HasExtension(std::string_view extension) const; + + private: + std::vector m_extensions; + }; + + enum class InstallerTypeEnum + { + Unknown, + Inno, + Wix, + Msi, + Nullsoft, + Zip, + Msix, + Exe, + Burn, + MSStore, + }; + + enum class UpdateBehaviorEnum + { + Unknown, + Install, + UninstallPrevious, + }; + + enum class InstallerSwitchType + { + Custom, + Silent, + SilentWithProgress, + Interactive, + Language, + Log, + InstallLocation, + Update, + }; + + enum class ScopeEnum + { + Unknown, + User, + Machine, + }; + + enum class InstallModeEnum + { + Unknown, + Interactive, + Silent, + SilentWithProgress, + }; + + enum class PlatformEnum + { + Unknown, + Universal, + Desktop, + }; + + enum class ManifestTypeEnum + { + Singleton, + Version, + Installer, + DefaultLocale, + Locale, + Merged, + Preview, + }; + + enum class DependencyType + { + WindowsFeature, + WindowsLibrary, + Package, + External + }; + + struct Dependency + { + DependencyType Type; + string_t Id; + std::optional MinVersion; + + Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(AppInstaller::Utility::Version(minVersion)) {} + Dependency(DependencyType type, string_t id) : Type(std::move(type)), Id(std::move(id)) {} + Dependency(DependencyType type) : Type(type) {} + + bool operator==(const Dependency& rhs) const { + return Type == rhs.Type && ICUCaseInsensitiveEquals(Id, rhs.Id); + } + + bool operator <(const Dependency& rhs) const + { + return Id < rhs.Id; + } + + bool IsVersionOk(string_t version) + { + return MinVersion <= AppInstaller::Utility::Version(version); + } + }; + + struct DependencyList + { + DependencyList() = default; + + void Add(const Dependency& newDependency) + { + Dependency* existingDependency = this->HasDependency(newDependency); + + if (existingDependency != NULL) { + if (newDependency.MinVersion) + { + if (existingDependency->MinVersion) + { + if (newDependency.MinVersion.value() > existingDependency->MinVersion.value()) + { + existingDependency->MinVersion.value() = newDependency.MinVersion.value(); + } + } + else + { + existingDependency->MinVersion.value() = newDependency.MinVersion.value(); + } + } + } + else + { + dependencies.push_back(newDependency); + } + } + + void Add(const DependencyList& otherDependencyList) + { + for (const auto& dependency : otherDependencyList.dependencies) + { + this->Add(dependency); + } + } + + bool HasAny() const { return !dependencies.empty(); } + bool HasAnyOf(DependencyType type) const + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type) return true; + }; + return false; + } + + Dependency* HasDependency(const Dependency& dependencyToSearch) + { + for (auto& dependency : dependencies) { + if (dependency == dependencyToSearch) + { + return &dependency; + } + } + return nullptr; + } + + void ApplyToType(DependencyType type, std::function func) const + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type) func(dependency); + } + } + + void ApplyToAll(std::function func) const + { + for (const auto& dependency : dependencies) + { + func(dependency); + } + } + + bool Empty() const + { + return dependencies.empty(); + } + + void Clear() { dependencies.clear(); } + + // for testing purposes + bool HasExactDependency(DependencyType type, string_t id, string_t minVersion = "") + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id, id)) + { + if (dependency.MinVersion) { + if (dependency.MinVersion.value() == AppInstaller::Utility::Version(minVersion)) + { + return true; + } + } + else { + return true; + } + } + } + return false; + } + + size_t Size() + { + return dependencies.size(); + } + + private: + std::vector dependencies; + }; + + struct DependencyGraph + { + // this constructor was intented for use during installation flow (we already have installer dependencies and there's no need to search the source again) + DependencyGraph(const Dependency& root, const DependencyList& rootDependencies, + std::function infoFunction) : m_root(root), getDependencies(infoFunction) + { + adjacents[m_root] = std::vector(); + toCheck = std::vector(); + rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + toCheck.push_back(dependency); + AddNode(dependency); + AddAdjacent(root, dependency); + }); + } + + DependencyGraph(const Dependency& root, std::function infoFunction) : m_root(root), getDependencies(infoFunction) + { + adjacents[m_root] = std::vector(); + toCheck = std::vector(); + + const DependencyList& rootDependencies = getDependencies(root); + rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + toCheck.push_back(dependency); + AddNode(dependency); + AddAdjacent(root, dependency); + }); + } + + void BuildGraph() + { + if (toCheck.empty()) + { + return; + } + + for (int i = 0; i < toCheck.size(); ++i) + { + auto node = toCheck.at(i); + + const auto& nodeDependencies = getDependencies(node); + //TODO add error stream so we can report back + + nodeDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) + { + if (!HasNode(dependency)) + { + toCheck.push_back(dependency); + AddNode(dependency); + } + + AddAdjacent(node, dependency); + }); + } + + CheckForLoopsAndGetOrder(); + } + + void AddNode(const Dependency& node) + { + adjacents[node] = std::vector(); + } + + void AddAdjacent(const Dependency& node,const Dependency& adjacent) + { + adjacents[node].push_back(adjacent); + } + + bool HasNode(const Dependency& dependency) + { + auto search = adjacents.find(dependency); + return search != adjacents.end(); + } + + bool HasLoop() + { + return hasLoop; + } + + void CheckForLoopsAndGetOrder() + { + installationOrder = std::vector(); + std::set visited; + hasLoop = HasLoopDFS(visited, m_root); + } + + std::vector GetInstallationOrder() + { + return installationOrder; + } + + private: + // TODO make this function iterative + bool HasLoopDFS(std::set visited, const Dependency& node) + { + // Adding before checking for loops, to have an order even if a loop is present + if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) + { + installationOrder.push_back(node); + } + + visited.insert(node); + for (const auto& adjacent : adjacents.at(node)) + { + auto search = visited.find(adjacent); + if (search == visited.end()) // if not found + { + if (HasLoopDFS(visited, adjacent)) + { + return true; + } + } + else + { + return true; + } + } + + return false; + } + + const Dependency& m_root; + std::map> adjacents; //(?) value should be a set instead of a vector? + std::function getDependencies; + + bool hasLoop; + std::vector installationOrder; + + std::vector toCheck; + std::map failedPackages; + }; + + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); + + UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); + + ScopeEnum ConvertToScopeEnum(std::string_view in); + + InstallModeEnum ConvertToInstallModeEnum(const std::string& in); + + PlatformEnum ConvertToPlatformEnum(const std::string& in); + + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); + + std::string_view InstallerTypeToString(InstallerTypeEnum installerType); + + std::string_view ScopeToString(ScopeEnum scope); + + // Gets a value indicating whether the given installer type uses the PackageFamilyName system reference. + bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type uses the ProductCode system reference. + bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); + + // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible + bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); + + // Get a list of default switches for known installer types + std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType); +} \ No newline at end of file diff --git a/src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp b/src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp new file mode 100644 index 0000000000..13b1425119 --- /dev/null +++ b/src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp @@ -0,0 +1,731 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "InstallFlow.h" +#include "UninstallFlow.h" +#include "Resources.h" +#include "ShellExecuteInstallerHandler.h" +#include "MSStoreInstallerHandler.h" +#include "WorkflowBase.h" +#include "DependenciesFlow.h" + +namespace AppInstaller::CLI::Workflow +{ + using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Foundation::Collections; + using namespace winrt::Windows::Management::Deployment; + using namespace AppInstaller::Utility; + using namespace AppInstaller::Manifest; + using namespace AppInstaller::Repository; + + namespace + { + bool MightWriteToARP(InstallerTypeEnum type) + { + switch (type) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: + return true; + default: + return false; + } + } + } + + void EnsureApplicableInstaller(Execution::Context& context) + { + const auto& installer = context.Get(); + + if (!installer.has_value()) + { + context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + } + } + + void ShowInstallationDisclaimer(Execution::Context& context) + { + auto installerType = context.Get().value().InstallerType; + + if (installerType == InstallerTypeEnum::MSStore) + { + context.Reporter.Info() << Resource::String::InstallationDisclaimerMSStore << std::endl; + } + else + { + context.Reporter.Info() << + Resource::String::InstallationDisclaimer1 << std::endl << + Resource::String::InstallationDisclaimer2 << std::endl; + } + } + + void DownloadInstaller(Execution::Context& context) + { + const auto& installer = context.Get().value(); + + switch (installer.InstallerType) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: + context << DownloadInstallerFile << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; + break; + case InstallerTypeEnum::Msix: + if (installer.SignatureSha256.empty()) + { + context << DownloadInstallerFile << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; + } + else + { + // Signature hash provided. No download needed. Just verify signature hash. + context << GetMsixSignatureHash << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; + } + break; + case InstallerTypeEnum::MSStore: + // Nothing to do here + break; + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void DownloadInstallerFile(Execution::Context& context) + { + const auto& manifest = context.Get(); + const auto& installer = context.Get().value(); + + std::filesystem::path tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); + tempInstallerPath /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); + + Utility::DownloadInfo downloadInfo{}; + downloadInfo.DisplayName = Resource::GetFixedString(Resource::FixedString::ProductName); + // Use the SHA256 hash of the installer as the identifier for the download + downloadInfo.ContentId = SHA256::ConvertToString(installer.Sha256); + + AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); + + context.Reporter.Info() << "Downloading " << Execution::UrlEmphasis << installer.Url << std::endl; + + std::optional> hash; + + const int MaxRetryCount = 2; + for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) + { + bool success = false; + try + { + hash = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download, + installer.Url, + tempInstallerPath, + Utility::DownloadType::Installer, + std::placeholders::_1, + true, + downloadInfo)); + + success = true; + } + catch (...) + { + if (retryCount < MaxRetryCount - 1) + { + AICLI_LOG(CLI, Info, << "Failed to download, waiting a bit and retry. Url: " << installer.Url); + Sleep(500); + } + else + { + throw; + } + } + + if (success) + { + break; + } + } + + if (!hash) + { + context.Reporter.Info() << "Package download canceled." << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + + context.Add(std::make_pair(installer.Sha256, hash.value())); + context.Add(std::move(tempInstallerPath)); + } + + void GetMsixSignatureHash(Execution::Context& context) + { + // We use this when the server won't support streaming install to swap to download. + bool downloadInstead = false; + + try + { + const auto& installer = context.Get().value(); + + Msix::MsixInfo msixInfo(installer.Url); + auto signature = msixInfo.GetSignature(); + + auto signatureHash = SHA256::ComputeHash(signature.data(), static_cast(signature.size())); + + context.Add(std::make_pair(installer.SignatureSha256, signatureHash)); + } + catch (const winrt::hresult_error& e) + { + if (static_cast(e.code()) == HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED) || + HRESULT_FACILITY(e.code()) == FACILITY_HTTP) + { + // Failed to get signature hash through HttpStream, use download + downloadInstead = true; + } + else + { + throw; + } + } + + if (downloadInstead) + { + context << DownloadInstallerFile; + } + } + + void VerifyInstallerHash(Execution::Context& context) + { + const auto& hashPair = context.Get(); + + if (!std::equal( + hashPair.first.begin(), + hashPair.first.end(), + hashPair.second.begin())) + { + bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride); + + const auto& manifest = context.Get(); + Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch); + + // If running as admin, do not allow the user to override the hash failure. + if (Runtime::IsRunningAsAdmin()) + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl; + } + else if (overrideHashMismatch) + { + context.Reporter.Warn() << Resource::String::InstallerHashMismatchOverridden << std::endl; + return; + } + else if (Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::HashOverride)) + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchOverrideRequired << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchError << std::endl; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); + } + else + { + AICLI_LOG(CLI, Info, << "Installer hash verified"); + context.Reporter.Info() << Resource::String::InstallerHashVerified << std::endl; + + context.SetFlags(Execution::ContextFlag::InstallerHashMatched); + + if (context.Contains(Execution::Data::PackageVersion) && + context.Get()->GetSource() != nullptr && + WI_IsFlagSet(context.Get()->GetSource()->GetDetails().TrustLevel, SourceTrustLevel::Trusted)) + { + context.SetFlags(Execution::ContextFlag::InstallerTrusted); + } + } + } + + void UpdateInstallerFileMotwIfApplicable(Execution::Context& context) + { + if (context.Contains(Execution::Data::InstallerPath)) + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted)) + { + Utility::ApplyMotwIfApplicable(context.Get(), URLZONE_TRUSTED); + } + else if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerHashMatched)) + { + const auto& installer = context.Get(); + HRESULT hr = Utility::ApplyMotwUsingIAttachmentExecuteIfApplicable(context.Get(), installer.value().Url); + + // Not using SUCCEEDED(hr) to check since there are cases file is missing after a successful scan + if (hr != S_OK) + { + switch (hr) + { + case INET_E_SECURITY_PROBLEM: + context.Reporter.Error() << Resource::String::InstallerBlockedByPolicy << std::endl; + break; + case E_FAIL: + context.Reporter.Error() << Resource::String::InstallerFailedVirusScan << std::endl; + break; + default: + context.Reporter.Error() << Resource::String::InstallerFailedSecurityCheck << std::endl; + } + + AICLI_LOG(Fail, Error, << "Installer failed security check. Url: " << installer.value().Url << " Result: " << WINGET_OSTREAM_FORMAT_HRESULT(hr)); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED); + } + } + } + } + + void ExecuteInstaller(Execution::Context& context) + { + const auto& installer = context.Get().value(); + + bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + + switch (installer.InstallerType) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: + if (isUpdate && installer.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious) + { + context << + GetUninstallInfo << + ExecuteUninstaller; + context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); + } + context << ShellExecuteInstall; + break; + case InstallerTypeEnum::Msix: + context << MsixInstall; + break; + case InstallerTypeEnum::MSStore: + context << + EnsureFeatureEnabled(Settings::ExperimentalFeature::Feature::ExperimentalMSStore) << + EnsureStorePolicySatisfied << + (isUpdate ? MSStoreUpdate : MSStoreInstall); + break; + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void ShellExecuteInstall(Execution::Context& context) + { + context << + GetInstallerArgs << + RenameDownloadedInstaller << + ShellExecuteInstallImpl; + } + + void MsixInstall(Execution::Context& context) + { + Uri uri = nullptr; + if (context.Contains(Execution::Data::InstallerPath)) + { + uri = Uri(context.Get().c_str()); + } + else + { + uri = Uri(Utility::ConvertToUTF16(context.Get()->Url)); + } + + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + try + { + DeploymentOptions deploymentOptions = + DeploymentOptions::ForceApplicationShutdown | + DeploymentOptions::ForceTargetApplicationShutdown; + + context.Reporter.ExecuteWithProgress(std::bind(Deployment::AddPackage, uri, deploymentOptions, + WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), std::placeholders::_1)); + } + catch (const wil::ResultException& re) + { + const auto& manifest = context.Get(); + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "MSIX", re.GetErrorCode()); + + context.Reporter.Error() << GetUserPresentableMessage(re) << std::endl; + AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); + } + + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } + + void RemoveInstaller(Execution::Context& context) + { + // Path may not be present if installed from a URL for MSIX + if (context.Contains(Execution::Data::InstallerPath)) + { + const auto& path = context.Get(); + AICLI_LOG(CLI, Info, << "Removing installer: " << path); + + try + { + // best effort + std::filesystem::remove(path); + } + catch (const std::exception& e) + { + AICLI_LOG(CLI, Warning, << "Failed to remove installer file after execution. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(CLI, Warning, << "Failed to remove installer file after execution. Reason unknown."); + } + } + } + + void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) + { + context << + Workflow::ReportManifestIdentity << + Workflow::ShowInstallationDisclaimer; + } + + void InstallPackageInstaller(Execution::Context& context) + { + context << + Workflow::ReportExecutionStage(ExecutionStage::Download) << + Workflow::DownloadInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PreExecution) << + Workflow::SnapshotARPEntries << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << + Workflow::ExecuteInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << + Workflow::ReportARPChanges << + Workflow::RemoveInstaller; + } + + void InstallPackageVersion(Execution::Context& context) + { + context << + Workflow::SelectInstaller << + Workflow::EnsureApplicableInstaller << + Workflow::ReportIdentityAndInstallationDisclaimer << + Workflow::GetDependenciesFromInstaller << + Workflow::ManagePackageDependencies << + Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + Workflow::InstallPackageInstaller; + } + + void SelectInstallerMultiple(Execution::Context& context) + { + bool allSucceeded = true; + DependencyList allDependencies; + std::vector installers; + + for (auto package : context.Get()) + { + Logging::SubExecutionTelemetryScope subExecution; + + // We want to do best effort to install all packages regardless of previous failures + auto installContextPtr = context.Clone(); + Execution::Context& installContext = *installContextPtr; + + // Extract the data needed for installing + installContext.Add(package.PackageVersion); + installContext.Add(package.PackageVersion->GetManifest()); + + // TODO: In the future, it would be better to not have to convert back and forth from a string + installContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(package.PackageRequest.Scope)); + + installContext << + Workflow::SelectInstaller << + Workflow::EnsureApplicableInstaller; + + if (installContext.IsTerminated()) + { + allSucceeded = false; + continue; + } + + const auto& installer = installContext.Get(); + installers.push_back({package.PackageVersion, installer.value(), false}); + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + allDependencies.Add(installer->Dependencies); + } + } + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + context.Add(allDependencies); + context << Workflow::ReportDependencies(Resource::String::ImportCommandReportDependencies); + } + context.Add(installers); + context << InstallMultiple; + } + + void InstallMultiple(Execution::Context& context) + { + bool allSucceeded = true; + + const auto& installers = context.Get(); + + for (auto packageAndInstaller : installers) + { + auto packageVersion = packageAndInstaller.PackageVersion; + auto installer = packageAndInstaller.Installer; + + auto installContextPtr = context.Clone(); + Execution::Context& installContext = *installContextPtr; + + // set data needed for installing + installContext.Add(packageVersion); + installContext.Add(packageVersion->GetManifest()); + installContext.Add(installer); + + installContext << + ReportIdentityAndInstallationDisclaimer << + Workflow::InstallPackageInstaller; + + if (installContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + + allSucceeded = false; + } + } + + if (!allSucceeded) + { + context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED); + } + } + + void SnapshotARPEntries(Execution::Context& context) try + { + // Ensure that installer type might actually write to ARP, otherwise this is a waste of time + auto installer = context.Get(); + + if (installer && MightWriteToARP(installer->InstallerType)) + { + std::shared_ptr arpSource = context.Reporter.ExecuteWithProgress( + [](IProgressCallback& progress) + { + return Repository::OpenPredefinedSource(PredefinedSource::ARP, progress); + }, true); + + std::vector> entries; + + for (const auto& entry : arpSource->Search({}).Matches) + { + auto installed = entry.Package->GetInstalledVersion(); + if (installed) + { + entries.emplace_back(std::make_tuple( + entry.Package->GetProperty(PackageProperty::Id), + installed->GetProperty(PackageVersionProperty::Version), + installed->GetProperty(PackageVersionProperty::Channel))); + } + } + + std::sort(entries.begin(), entries.end()); + + context.Add(std::move(entries)); + } + } + CATCH_LOG() + + void ReportARPChanges(Execution::Context& context) try + { + if (context.Contains(Execution::Data::ARPSnapshot)) + { + const auto& entries = context.Get(); + + // Open it again to get the (potentially) changed ARP entries + std::shared_ptr arpSource = context.Reporter.ExecuteWithProgress( + [](IProgressCallback& progress) + { + return Repository::OpenPredefinedSource(PredefinedSource::ARP, progress); + }, true); + + std::vector changes; + + for (auto& entry : arpSource->Search({}).Matches) + { + auto installed = entry.Package->GetInstalledVersion(); + + if (installed) + { + auto entryKey = std::make_tuple( + entry.Package->GetProperty(PackageProperty::Id), + installed->GetProperty(PackageVersionProperty::Version), + installed->GetProperty(PackageVersionProperty::Channel)); + + auto itr = std::lower_bound(entries.begin(), entries.end(), entryKey); + if (itr == entries.end() || *itr != entryKey) + { + changes.emplace_back(std::move(entry)); + } + } + } + + // Also attempt to find the entry based on the manifest data + const auto& manifest = context.Get(); + + SearchRequest nameAndPublisherRequest; + + // The default localization must contain the name or we cannot do this lookup + if (manifest.DefaultLocalization.Contains(Localization::PackageName)) + { + AppInstaller::Manifest::Manifest::string_t defaultName = manifest.DefaultLocalization.Get(); + AppInstaller::Manifest::Manifest::string_t defaultPublisher; + if (manifest.DefaultLocalization.Contains(Localization::Publisher)) + { + defaultPublisher = manifest.DefaultLocalization.Get(); + } + + nameAndPublisherRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, defaultName, defaultPublisher)); + + for (const auto& loc : manifest.Localizations) + { + if (loc.Contains(Localization::PackageName) || loc.Contains(Localization::Publisher)) + { + nameAndPublisherRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, + loc.Contains(Localization::PackageName) ? loc.Get() : defaultName, + loc.Contains(Localization::Publisher) ? loc.Get() : defaultPublisher)); + } + } + } + + std::vector productCodes; + for (const auto& installer : manifest.Installers) + { + if (!installer.ProductCode.empty()) + { + if (std::find(productCodes.begin(), productCodes.end(), installer.ProductCode) == productCodes.end()) + { + nameAndPublisherRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode)); + productCodes.emplace_back(installer.ProductCode); + } + } + } + + SearchResult findByManifest; + + // Don't execute this search if it would just find everything + if (!nameAndPublisherRequest.IsForEverything()) + { + findByManifest = arpSource->Search(nameAndPublisherRequest); + } + + // Cross reference the changes with the search results + std::vector> packagesInBoth; + + for (const auto& change : changes) + { + for (const auto& byManifest : findByManifest.Matches) + { + if (change.Package->IsSame(byManifest.Package.get())) + { + packagesInBoth.emplace_back(change.Package); + break; + } + } + } + + // We now have all of the package changes; time to report them. + // The set of cases we could have for changes to ARP: + // 0 packages :: No changes were detected to ARP, which could mean that the installer + // did not write an entry. It could also be a forced reinstall. + // 1 package :: Golden path; this should be what we installed. + // 2+ packages :: We need to determine which package actually matches the one that we + // were installing. + // + // The set of cases we could have for finding packages based on the manifest: + // 0 packages :: The manifest data does not match the ARP information. + // 1 package :: Golden path; this should be what we installed. + // 2+ packages :: The data in the manifest is either too broad or we have + // a problem with our name normalization. + // + // ARP Package changes + // 0 1 N + // +------------------+--------------------+--------------------+ + // M | | | | + // a | Package does not | Manifest data does | Manifest data does | + // n 0 | write to ARP | not match ARP | not match ARP | + // i | Log this fact | Log for fixup | Log for fixup | + // f | | | | + // e +------------------+--------------------+--------------------+ + // s | | | | + // t | Reinstall of | Golden Path! | Treat manifest as | + // 1 | existing version | (assuming match) | main if common | + // r | | | | + // e +------------------+--------------------+--------------------+ + // s | | | | + // u | Not expected | Treat ARP as main | Not expected | + // l N | Log this for | | Log this for | + // t | investigation | | investigation | + // s | | | | + // +------------------+--------------------+--------------------+ + + // Find the package that we are going to log + std::shared_ptr toLog; + + // If no changes found, only log if a single matching package was found by the manifest + if (changes.empty() && findByManifest.Matches.size() == 1) + { + toLog = findByManifest.Matches[0].Package->GetInstalledVersion(); + } + // If only a single ARP entry was changed, always log that + else if (changes.size() == 1) + { + toLog = changes[0].Package->GetInstalledVersion(); + } + // Finally, if there is only a single common package, log that one + else if (packagesInBoth.size() == 1) + { + toLog = packagesInBoth[0]->GetInstalledVersion(); + } + + IPackageVersion::Metadata toLogMetadata; + if (toLog) + { + toLogMetadata = toLog->GetMetadata(); + } + + // We can only get the source identifier from an active source + std::string sourceIdentifier; + if (context.Contains(Execution::Data::PackageVersion)) + { + sourceIdentifier = context.Get()->GetProperty(PackageVersionProperty::SourceIdentifier); + } + + Logging::Telemetry().LogSuccessfulInstallARPChange( + sourceIdentifier, + manifest.Id, + manifest.Version, + manifest.Channel, + changes.size(), + findByManifest.Matches.size(), + packagesInBoth.size(), + toLog ? static_cast(toLog->GetProperty(PackageVersionProperty::Name)) : "", + toLog ? static_cast(toLog->GetProperty(PackageVersionProperty::Version)) : "", + toLog ? static_cast(toLogMetadata[PackageVersionMetadata::Publisher]) : "", + toLog ? static_cast(toLogMetadata[PackageVersionMetadata::InstalledLocale]) : "" + ); + } + } + CATCH_LOG() +} From 48fbb52f96b72242a6d4dd4fd53911a04841843e Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 29 Jul 2021 17:58:53 -0300 Subject: [PATCH 35/41] dependency graph + installation, all tests passed --- .../Workflows/WorkflowBase.cpp | 2 +- src/AppInstallerCLITests/WorkFlow.cpp | 35 +++++++++++++++---- .../Public/winget/ManifestCommon.h | 20 ++++++----- 3 files changed, 41 insertions(+), 16 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index f4109ad169..b07bdd2e3a 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -146,7 +146,7 @@ namespace AppInstaller::CLI::Workflow std::string_view sourceName; if (m_forDependencies) { - if (m_forDependencies && context.Args.Contains(Execution::Args::Type::DependencySource)) + if (context.Args.Contains(Execution::Args::Type::DependencySource)) { sourceName = context.Args.GetArg(Execution::Args::Type::DependencySource); } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index f6961a7536..a37f0c84f7 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -469,12 +470,25 @@ void OverrideForImportSource(TestContext& context) } }); } -void OverrideForDependencySource(TestContext& context) +void OverrideOpenSourceForDependencies(TestContext& context) { context.Override({ "OpenSource", [](TestContext& context) { context.Add(std::make_shared()); } }); + + context.Override({ Workflow::OpenDependencySource, [](TestContext& context) + { + context.Add(std::make_shared()); + } }); +} + +void OverrideDependencySource(TestContext& context) +{ + context.Override({ Workflow::OpenDependencySource, [](TestContext& context) + { + context.Add(std::make_shared()); + } }); } void OverrideForUpdateInstallerMotw(TestContext& context) @@ -1753,6 +1767,7 @@ TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideForShellExecute(context); + OverrideDependencySource(context); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); @@ -1774,7 +1789,7 @@ TEST_CASE("DependencyGraph_Loop", "[InstallFlow][workflow][dependencyGraph][depe std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; - OverrideForDependencySource(context); + OverrideOpenSourceForDependencies(context); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, "EasyToSeeLoop"sv); @@ -1795,7 +1810,7 @@ TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGr std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; - OverrideForDependencySource(context); + OverrideOpenSourceForDependencies(context); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, "DependencyAlreadyInStackButNoLoop"sv); @@ -1817,7 +1832,7 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; - OverrideForDependencySource(context); + OverrideOpenSourceForDependencies(context); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, "PathBetweenBranchesButNoLoop"sv); @@ -1840,7 +1855,7 @@ TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyG std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; - OverrideForDependencySource(context); + OverrideOpenSourceForDependencies(context); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); @@ -1863,7 +1878,7 @@ TEST_CASE("DependencyGraph_BFirst", "[InstallFlow][workflow][dependencyGraph][de std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; - OverrideForDependencySource(context); + OverrideOpenSourceForDependencies(context); OverrideForShellExecute(context); context.Args.AddArg(Execution::Args::Type::Query, "NeedsToInstallBFirst"sv); @@ -1911,6 +1926,7 @@ TEST_CASE("DependenciesMultideclaration_InstallerDependenciesPreference", "[depe std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideForShellExecute(context); + OverrideDependencySource(context); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesMultideclaration.yaml").GetPath().u8string()); @@ -1935,6 +1951,7 @@ TEST_CASE("InstallerWithoutDependencies_RootDependenciesAreUsed", "[dependencies std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideForShellExecute(context); + OverrideDependencySource(context); context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesOnRoot.yaml").GetPath().u8string()); @@ -1948,4 +1965,8 @@ TEST_CASE("InstallerWithoutDependencies_RootDependenciesAreUsed", "[dependencies // Verify root dependencies are shown REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); REQUIRE(installOutput.str().find("PreviewIISOnRoot") != std::string::npos); -} \ No newline at end of file +} +// TODO +// add dependencies for installer tests to DependenciesTestSource (or a new one) +// add tests for min version dependency solving +// add tests that check for correct installation of dependencies (not only the order) \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 83bcde2eca..8d940fb59b 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -354,11 +354,7 @@ bool HasExtension(std::string_view extension) const; // TODO make this function iterative bool HasLoopDFS(std::set visited, const Dependency& node) { - // Adding before checking for loops, to have an order even if a loop is present - if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) - { - installationOrder.push_back(node); - } + bool loop = false; visited.insert(node); for (const auto& adjacent : adjacents.at(node)) @@ -368,16 +364,24 @@ bool HasExtension(std::string_view extension) const; { if (HasLoopDFS(visited, adjacent)) { - return true; + loop = true; + // didn't break the loop to have a complete order at the end (even if a loop exists) } } else { - return true; + loop = true; + // didn't break the loop to have a complete order at the end (even if a loop exists) } } - return false; + // Adding to have an order even if a loop is present + if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) + { + installationOrder.push_back(node); + } + + return loop; } const Dependency& m_root; From 43099edd818b19ce07bdc92824b167ac1b5ed4b1 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 29 Jul 2021 18:42:00 -0300 Subject: [PATCH 36/41] information detail, delete temp folder --- .../Workflows/DependenciesFlow.cpp | 2 + .../Workflows/InstallFlow.cpp | 2 +- src/AppInstallerCLITests/WorkFlow.cpp | 1 + .../DependenciesFlow.cpp | 242 ------ .../ManifestCommon.h | 421 ---------- .../InstallFlow.cpp | 731 ------------------ 6 files changed, 4 insertions(+), 1395 deletions(-) delete mode 100644 src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp delete mode 100644 src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h delete mode 100644 src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 070672eb61..6785415356 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -126,6 +126,8 @@ namespace AppInstaller::CLI::Workflow return; } + info << "Installing dependencies:" << std::endl; //TODO localize message + context << OpenDependencySource; if (!context.Contains(Execution::Data::DependencySource)) { diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 15f462ab07..6248ff72fa 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -416,8 +416,8 @@ namespace AppInstaller::CLI::Workflow Workflow::EnsureApplicableInstaller << Workflow::ReportIdentityAndInstallationDisclaimer << Workflow::GetDependenciesFromInstaller << - Workflow::ManagePackageDependencies << Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + Workflow::ManagePackageDependencies << Workflow::InstallPackageInstaller; } diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index a37f0c84f7..4d61222ee6 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -265,6 +265,7 @@ namespace // TODO maybe change package name on default locale for better debugging auto& installer = manifest.Installers.at(0); + installer.ProductId = input; installer.Dependencies.Clear(); /* diff --git a/src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp b/src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp deleted file mode 100644 index 22bcb0b28f..0000000000 --- a/src/enc_temp_folder/1c45dac4c6c8f36dc14a122fd6f7e/DependenciesFlow.cpp +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "DependenciesFlow.h" -#include "ManifestComparator.h" -#include "InstallFlow.h" - - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::Repository; - using namespace Manifest; - - void ReportDependencies::operator()(Execution::Context& context) const - { - if (!Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) - { - return; - } - auto info = context.Reporter.Info(); - - const auto& dependencies = context.Get(); - if (dependencies.HasAny()) - { - info << Resource::StringId(m_messageId) << std::endl; - - if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) - { - info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; - dependencies.ApplyToType(DependencyType::WindowsFeature, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); - } - - if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) - { - info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; - dependencies.ApplyToType(DependencyType::WindowsLibrary, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); - } - - if (dependencies.HasAnyOf(DependencyType::Package)) - { - info << " - " << Resource::String::PackageDependencies << std::endl; - dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) - { - info << " " << dependency.Id; - if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value().ToString() << "]"; - info << std::endl; - }); - } - - if (dependencies.HasAnyOf(DependencyType::External)) - { - info << " - " << Resource::String::ExternalDependencies << std::endl; - dependencies.ApplyToType(DependencyType::External, [&info](Dependency dependency) {info << " " << dependency.Id << std::endl; }); - } - } - } - - void GetInstallersDependenciesFromManifest(Execution::Context& context) { - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) - { - const auto& manifest = context.Get(); - DependencyList allDependencies; - - for (const auto& installer : manifest.Installers) - { - allDependencies.Add(installer.Dependencies); - } - - context.Add(allDependencies); - } - } - - void GetDependenciesFromInstaller(Execution::Context& context) - { - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) - { - const auto& installer = context.Get(); - if (installer) - { - context.Add(installer->Dependencies); - } - } - } - - void GetDependenciesInfoForUninstall(Execution::Context& context) - { - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) - { - // 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 - context.Add(DependencyList()); // sending empty list of dependencies for now - } - } - - void OpenDependencySource(Execution::Context& context) - { - if (context.Contains(Execution::Data::PackageVersion)) - { - const auto& packageVersion = context.Get(); - context.Add(packageVersion->GetSource()); - context << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); - } - else - { // install from manifest requires --dependency-source to be set - context << - Workflow::OpenSource(true) << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true); - } - } - - - void ManagePackageDependencies(Execution::Context& context) - { - auto info = context.Reporter.Info(); - const auto& rootManifest = context.Get(); - - Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); - - const auto& rootInstaller = context.Get(); - const auto& rootDependencies = rootInstaller->Dependencies; - - if (rootDependencies.Empty()) - { - // If there's no dependencies there's nothing to do aside of logging the outcome - return; - } - - context << OpenDependencySource; - if (!context.Contains(Execution::Data::DependencySource)) - { - info << "dependency source not found" << std::endl; //TODO localize message - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); // TODO create specific error code - } - - const auto& source = context.Get(); - std::map idToInstallerMap; - - DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, - [&](Dependency node) { - auto info = context.Reporter.Info(); - - SearchRequest searchRequest; - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, node.Id)); - //TODO add min version filter to search request ? - const auto& matches = source->Search(searchRequest).Matches; - - if (!matches.empty()) - { - if (matches.size() > 1) { - info << "Too many matches"; //TODO localize all errors - return DependencyList(); //return empty dependency list, TODO change this to actually manage errors - } - const auto& match = matches.at(0); - - const auto& package = match.Package; - if (package->GetInstalledVersion() && node.IsVersionOk(package->GetInstalledVersion()->GetManifest().Version)) - { - return DependencyList(); //return empty dependency list, as we won't keep searching for dependencies for installed packages - } - else - { - const auto& latestVersion = package->GetLatestAvailableVersion(); - if (!latestVersion) { - info << "No package version found"; //TODO localize all errors - return DependencyList(); //return empty dependency list, TODO change this to actually manage errors - } - - const auto& manifest = latestVersion->GetManifest(); - if (manifest.Installers.empty()) { - info << "No installers found"; //TODO localize all errors - return DependencyList(); //return empty dependency list, TODO change this to actually manage errors - } - - if (!node.IsVersionOk(manifest.Version)) - { - info << "Minimum required version not available"; //TODO localize all errors - return DependencyList(); //return empty dependency list, TODO change this to actually manage errors - } - - std::optional installer; - bool isUpdate = false; - if (package->GetInstalledVersion()) - { - installer = SelectInstallerFromMetadata(context.Args, manifest, package->GetInstalledVersion()->GetMetadata()); - isUpdate = true; - } - else - { - installer = SelectInstallerFromMetadata(context.Args, manifest, {}); - } - - const auto& nodeDependencies = installer->Dependencies; - - idToInstallerMap[node.Id] = {latestVersion, installer.value(), isUpdate}; - return nodeDependencies; - } - } - else - { - info << "No matches"; //TODO localize all errors - return DependencyList(); //return empty dependency list, TODO change this to actually manage errors - } - }); - - dependencyGraph.BuildGraph(); // maybe it's better if it already does it on the constructor? - - if (dependencyGraph.HasLoop()) - { - info << "has loop" << std::endl; - Logging::Log().Write(Logging::Channel::CLI, Logging::Level::Warning, "Dependency loop found"); //TODO localization - //warn user but try to install either way - } - - // TODO raise error for failed packages? (if there's at least one) - - const auto& installationOrder = dependencyGraph.GetInstallationOrder(); - - std::vector installers; - - info << "order: "; //-- only for debugging - for (auto const& node : installationOrder) - { - info << node.Id << ", "; //-- only for debugging - - auto itr = idToInstallerMap.find(node.Id); - // if the package was already installed (with a useful version) or is the root - // then there will be no installer for it on the map. - if (itr != idToInstallerMap.end()) - { - installers.push_back(itr->second); - } - } - info << std::endl; //-- only for debugging - - // Install dependencies in the correct order - context.Add(installers); - context << InstallMultiple; - - // Install the root (continue) - } -} \ No newline at end of file diff --git a/src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h b/src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h deleted file mode 100644 index 3173c444f2..0000000000 --- a/src/enc_temp_folder/473be0fecfa66ed67c7cbecefcb3030/ManifestCommon.h +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Manifest -{ - using string_t = Utility::NormalizedString; - using namespace std::string_view_literals; - - // The maximum supported major version known about by this code. - constexpr uint64_t s_MaxSupportedMajorVersion = 1; - - // The default manifest version assigned to manifests without a ManifestVersion field. - constexpr std::string_view s_DefaultManifestVersion = "0.1.0"sv; - - // V1 manifest version for GA - constexpr std::string_view s_ManifestVersionV1 = "1.0.0"sv; - - // The manifest extension for the MS Store - constexpr std::string_view s_MSStoreExtension = "msstore"sv; - - // ManifestVer is inherited from Utility::Version and is a more restricted version. - // ManifestVer is used to specify the version of app manifest itself. - // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] - // and optionally a following tag in the format of -[SomeString] for experimental purpose. - struct ManifestVer : public Utility::Version - { - ManifestVer() = default; - - ManifestVer(std::string_view version); - - uint64_t Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } - uint64_t Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } - uint64_t Patch() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } - - bool HasExtension() const; - -bool HasExtension(std::string_view extension) const; - - private: - std::vector m_extensions; - }; - - enum class InstallerTypeEnum - { - Unknown, - Inno, - Wix, - Msi, - Nullsoft, - Zip, - Msix, - Exe, - Burn, - MSStore, - }; - - enum class UpdateBehaviorEnum - { - Unknown, - Install, - UninstallPrevious, - }; - - enum class InstallerSwitchType - { - Custom, - Silent, - SilentWithProgress, - Interactive, - Language, - Log, - InstallLocation, - Update, - }; - - enum class ScopeEnum - { - Unknown, - User, - Machine, - }; - - enum class InstallModeEnum - { - Unknown, - Interactive, - Silent, - SilentWithProgress, - }; - - enum class PlatformEnum - { - Unknown, - Universal, - Desktop, - }; - - enum class ManifestTypeEnum - { - Singleton, - Version, - Installer, - DefaultLocale, - Locale, - Merged, - Preview, - }; - - enum class DependencyType - { - WindowsFeature, - WindowsLibrary, - Package, - External - }; - - struct Dependency - { - DependencyType Type; - string_t Id; - std::optional MinVersion; - - Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(AppInstaller::Utility::Version(minVersion)) {} - Dependency(DependencyType type, string_t id) : Type(std::move(type)), Id(std::move(id)) {} - Dependency(DependencyType type) : Type(type) {} - - bool operator==(const Dependency& rhs) const { - return Type == rhs.Type && ICUCaseInsensitiveEquals(Id, rhs.Id); - } - - bool operator <(const Dependency& rhs) const - { - return Id < rhs.Id; - } - - bool IsVersionOk(string_t version) - { - return MinVersion <= AppInstaller::Utility::Version(version); - } - }; - - struct DependencyList - { - DependencyList() = default; - - void Add(const Dependency& newDependency) - { - Dependency* existingDependency = this->HasDependency(newDependency); - - if (existingDependency != NULL) { - if (newDependency.MinVersion) - { - if (existingDependency->MinVersion) - { - if (newDependency.MinVersion.value() > existingDependency->MinVersion.value()) - { - existingDependency->MinVersion.value() = newDependency.MinVersion.value(); - } - } - else - { - existingDependency->MinVersion.value() = newDependency.MinVersion.value(); - } - } - } - else - { - dependencies.push_back(newDependency); - } - } - - void Add(const DependencyList& otherDependencyList) - { - for (const auto& dependency : otherDependencyList.dependencies) - { - this->Add(dependency); - } - } - - bool HasAny() const { return !dependencies.empty(); } - bool HasAnyOf(DependencyType type) const - { - for (const auto& dependency : dependencies) - { - if (dependency.Type == type) return true; - }; - return false; - } - - Dependency* HasDependency(const Dependency& dependencyToSearch) - { - for (auto& dependency : dependencies) { - if (dependency == dependencyToSearch) - { - return &dependency; - } - } - return nullptr; - } - - void ApplyToType(DependencyType type, std::function func) const - { - for (const auto& dependency : dependencies) - { - if (dependency.Type == type) func(dependency); - } - } - - void ApplyToAll(std::function func) const - { - for (const auto& dependency : dependencies) - { - func(dependency); - } - } - - bool Empty() const - { - return dependencies.empty(); - } - - void Clear() { dependencies.clear(); } - - // for testing purposes - bool HasExactDependency(DependencyType type, string_t id, string_t minVersion = "") - { - for (const auto& dependency : dependencies) - { - if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id, id)) - { - if (dependency.MinVersion) { - if (dependency.MinVersion.value() == AppInstaller::Utility::Version(minVersion)) - { - return true; - } - } - else { - return true; - } - } - } - return false; - } - - size_t Size() - { - return dependencies.size(); - } - - private: - std::vector dependencies; - }; - - struct DependencyGraph - { - // this constructor was intented for use during installation flow (we already have installer dependencies and there's no need to search the source again) - DependencyGraph(const Dependency& root, const DependencyList& rootDependencies, - std::function infoFunction) : m_root(root), getDependencies(infoFunction) - { - adjacents[m_root] = std::vector(); - toCheck = std::vector(); - rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) - { - toCheck.push_back(dependency); - AddNode(dependency); - AddAdjacent(root, dependency); - }); - } - - DependencyGraph(const Dependency& root, std::function infoFunction) : m_root(root), getDependencies(infoFunction) - { - adjacents[m_root] = std::vector(); - toCheck = std::vector(); - - const DependencyList& rootDependencies = getDependencies(root); - rootDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) - { - toCheck.push_back(dependency); - AddNode(dependency); - AddAdjacent(root, dependency); - }); - } - - void BuildGraph() - { - if (toCheck.empty()) - { - return; - } - - for (int i = 0; i < toCheck.size(); ++i) - { - auto node = toCheck.at(i); - - const auto& nodeDependencies = getDependencies(node); - //TODO add error stream so we can report back - - nodeDependencies.ApplyToType(DependencyType::Package, [&](Dependency dependency) - { - if (!HasNode(dependency)) - { - toCheck.push_back(dependency); - AddNode(dependency); - } - - AddAdjacent(node, dependency); - }); - } - - CheckForLoopsAndGetOrder(); - } - - void AddNode(const Dependency& node) - { - adjacents[node] = std::vector(); - } - - void AddAdjacent(const Dependency& node,const Dependency& adjacent) - { - adjacents[node].push_back(adjacent); - } - - bool HasNode(const Dependency& dependency) - { - auto search = adjacents.find(dependency); - return search != adjacents.end(); - } - - bool HasLoop() - { - return hasLoop; - } - - void CheckForLoopsAndGetOrder() - { - installationOrder = std::vector(); - std::set visited; - hasLoop = HasLoopDFS(visited, m_root); - } - - std::vector GetInstallationOrder() - { - return installationOrder; - } - - private: - // TODO make this function iterative - bool HasLoopDFS(std::set visited, const Dependency& node) - { - // Adding before checking for loops, to have an order even if a loop is present - if (std::find(installationOrder.begin(), installationOrder.end(), node) == installationOrder.end()) - { - installationOrder.push_back(node); - } - - visited.insert(node); - for (const auto& adjacent : adjacents.at(node)) - { - auto search = visited.find(adjacent); - if (search == visited.end()) // if not found - { - if (HasLoopDFS(visited, adjacent)) - { - return true; - } - } - else - { - return true; - } - } - - return false; - } - - const Dependency& m_root; - std::map> adjacents; //(?) value should be a set instead of a vector? - std::function getDependencies; - - bool hasLoop; - std::vector installationOrder; - - std::vector toCheck; - std::map failedPackages; - }; - - InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); - - UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); - - ScopeEnum ConvertToScopeEnum(std::string_view in); - - InstallModeEnum ConvertToInstallModeEnum(const std::string& in); - - PlatformEnum ConvertToPlatformEnum(const std::string& in); - - ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); - - std::string_view InstallerTypeToString(InstallerTypeEnum installerType); - - std::string_view ScopeToString(ScopeEnum scope); - - // Gets a value indicating whether the given installer type uses the PackageFamilyName system reference. - bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer type uses the ProductCode system reference. - bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); - - // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible - bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); - - // Get a list of default switches for known installer types - std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType); -} \ No newline at end of file diff --git a/src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp b/src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp deleted file mode 100644 index 13b1425119..0000000000 --- a/src/enc_temp_folder/e3c44113fafe4b1a4257475e59e387fc/InstallFlow.cpp +++ /dev/null @@ -1,731 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "InstallFlow.h" -#include "UninstallFlow.h" -#include "Resources.h" -#include "ShellExecuteInstallerHandler.h" -#include "MSStoreInstallerHandler.h" -#include "WorkflowBase.h" -#include "DependenciesFlow.h" - -namespace AppInstaller::CLI::Workflow -{ - using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Foundation::Collections; - using namespace winrt::Windows::Management::Deployment; - using namespace AppInstaller::Utility; - using namespace AppInstaller::Manifest; - using namespace AppInstaller::Repository; - - namespace - { - bool MightWriteToARP(InstallerTypeEnum type) - { - switch (type) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Wix: - return true; - default: - return false; - } - } - } - - void EnsureApplicableInstaller(Execution::Context& context) - { - const auto& installer = context.Get(); - - if (!installer.has_value()) - { - context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); - } - } - - void ShowInstallationDisclaimer(Execution::Context& context) - { - auto installerType = context.Get().value().InstallerType; - - if (installerType == InstallerTypeEnum::MSStore) - { - context.Reporter.Info() << Resource::String::InstallationDisclaimerMSStore << std::endl; - } - else - { - context.Reporter.Info() << - Resource::String::InstallationDisclaimer1 << std::endl << - Resource::String::InstallationDisclaimer2 << std::endl; - } - } - - void DownloadInstaller(Execution::Context& context) - { - const auto& installer = context.Get().value(); - - switch (installer.InstallerType) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Wix: - context << DownloadInstallerFile << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; - break; - case InstallerTypeEnum::Msix: - if (installer.SignatureSha256.empty()) - { - context << DownloadInstallerFile << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; - } - else - { - // Signature hash provided. No download needed. Just verify signature hash. - context << GetMsixSignatureHash << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; - } - break; - case InstallerTypeEnum::MSStore: - // Nothing to do here - break; - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void DownloadInstallerFile(Execution::Context& context) - { - const auto& manifest = context.Get(); - const auto& installer = context.Get().value(); - - std::filesystem::path tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); - tempInstallerPath /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); - - Utility::DownloadInfo downloadInfo{}; - downloadInfo.DisplayName = Resource::GetFixedString(Resource::FixedString::ProductName); - // Use the SHA256 hash of the installer as the identifier for the download - downloadInfo.ContentId = SHA256::ConvertToString(installer.Sha256); - - AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); - - context.Reporter.Info() << "Downloading " << Execution::UrlEmphasis << installer.Url << std::endl; - - std::optional> hash; - - const int MaxRetryCount = 2; - for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) - { - bool success = false; - try - { - hash = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download, - installer.Url, - tempInstallerPath, - Utility::DownloadType::Installer, - std::placeholders::_1, - true, - downloadInfo)); - - success = true; - } - catch (...) - { - if (retryCount < MaxRetryCount - 1) - { - AICLI_LOG(CLI, Info, << "Failed to download, waiting a bit and retry. Url: " << installer.Url); - Sleep(500); - } - else - { - throw; - } - } - - if (success) - { - break; - } - } - - if (!hash) - { - context.Reporter.Info() << "Package download canceled." << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - - context.Add(std::make_pair(installer.Sha256, hash.value())); - context.Add(std::move(tempInstallerPath)); - } - - void GetMsixSignatureHash(Execution::Context& context) - { - // We use this when the server won't support streaming install to swap to download. - bool downloadInstead = false; - - try - { - const auto& installer = context.Get().value(); - - Msix::MsixInfo msixInfo(installer.Url); - auto signature = msixInfo.GetSignature(); - - auto signatureHash = SHA256::ComputeHash(signature.data(), static_cast(signature.size())); - - context.Add(std::make_pair(installer.SignatureSha256, signatureHash)); - } - catch (const winrt::hresult_error& e) - { - if (static_cast(e.code()) == HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED) || - HRESULT_FACILITY(e.code()) == FACILITY_HTTP) - { - // Failed to get signature hash through HttpStream, use download - downloadInstead = true; - } - else - { - throw; - } - } - - if (downloadInstead) - { - context << DownloadInstallerFile; - } - } - - void VerifyInstallerHash(Execution::Context& context) - { - const auto& hashPair = context.Get(); - - if (!std::equal( - hashPair.first.begin(), - hashPair.first.end(), - hashPair.second.begin())) - { - bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride); - - const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, hashPair.first, hashPair.second, overrideHashMismatch); - - // If running as admin, do not allow the user to override the hash failure. - if (Runtime::IsRunningAsAdmin()) - { - context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl; - } - else if (overrideHashMismatch) - { - context.Reporter.Warn() << Resource::String::InstallerHashMismatchOverridden << std::endl; - return; - } - else if (Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::HashOverride)) - { - context.Reporter.Error() << Resource::String::InstallerHashMismatchOverrideRequired << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::InstallerHashMismatchError << std::endl; - } - - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); - } - else - { - AICLI_LOG(CLI, Info, << "Installer hash verified"); - context.Reporter.Info() << Resource::String::InstallerHashVerified << std::endl; - - context.SetFlags(Execution::ContextFlag::InstallerHashMatched); - - if (context.Contains(Execution::Data::PackageVersion) && - context.Get()->GetSource() != nullptr && - WI_IsFlagSet(context.Get()->GetSource()->GetDetails().TrustLevel, SourceTrustLevel::Trusted)) - { - context.SetFlags(Execution::ContextFlag::InstallerTrusted); - } - } - } - - void UpdateInstallerFileMotwIfApplicable(Execution::Context& context) - { - if (context.Contains(Execution::Data::InstallerPath)) - { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted)) - { - Utility::ApplyMotwIfApplicable(context.Get(), URLZONE_TRUSTED); - } - else if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerHashMatched)) - { - const auto& installer = context.Get(); - HRESULT hr = Utility::ApplyMotwUsingIAttachmentExecuteIfApplicable(context.Get(), installer.value().Url); - - // Not using SUCCEEDED(hr) to check since there are cases file is missing after a successful scan - if (hr != S_OK) - { - switch (hr) - { - case INET_E_SECURITY_PROBLEM: - context.Reporter.Error() << Resource::String::InstallerBlockedByPolicy << std::endl; - break; - case E_FAIL: - context.Reporter.Error() << Resource::String::InstallerFailedVirusScan << std::endl; - break; - default: - context.Reporter.Error() << Resource::String::InstallerFailedSecurityCheck << std::endl; - } - - AICLI_LOG(Fail, Error, << "Installer failed security check. Url: " << installer.value().Url << " Result: " << WINGET_OSTREAM_FORMAT_HRESULT(hr)); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED); - } - } - } - } - - void ExecuteInstaller(Execution::Context& context) - { - const auto& installer = context.Get().value(); - - bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); - - switch (installer.InstallerType) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Wix: - if (isUpdate && installer.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious) - { - context << - GetUninstallInfo << - ExecuteUninstaller; - context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); - } - context << ShellExecuteInstall; - break; - case InstallerTypeEnum::Msix: - context << MsixInstall; - break; - case InstallerTypeEnum::MSStore: - context << - EnsureFeatureEnabled(Settings::ExperimentalFeature::Feature::ExperimentalMSStore) << - EnsureStorePolicySatisfied << - (isUpdate ? MSStoreUpdate : MSStoreInstall); - break; - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void ShellExecuteInstall(Execution::Context& context) - { - context << - GetInstallerArgs << - RenameDownloadedInstaller << - ShellExecuteInstallImpl; - } - - void MsixInstall(Execution::Context& context) - { - Uri uri = nullptr; - if (context.Contains(Execution::Data::InstallerPath)) - { - uri = Uri(context.Get().c_str()); - } - else - { - uri = Uri(Utility::ConvertToUTF16(context.Get()->Url)); - } - - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - - try - { - DeploymentOptions deploymentOptions = - DeploymentOptions::ForceApplicationShutdown | - DeploymentOptions::ForceTargetApplicationShutdown; - - context.Reporter.ExecuteWithProgress(std::bind(Deployment::AddPackage, uri, deploymentOptions, - WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted), std::placeholders::_1)); - } - catch (const wil::ResultException& re) - { - const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, "MSIX", re.GetErrorCode()); - - context.Reporter.Error() << GetUserPresentableMessage(re) << std::endl; - AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); - } - - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; - } - - void RemoveInstaller(Execution::Context& context) - { - // Path may not be present if installed from a URL for MSIX - if (context.Contains(Execution::Data::InstallerPath)) - { - const auto& path = context.Get(); - AICLI_LOG(CLI, Info, << "Removing installer: " << path); - - try - { - // best effort - std::filesystem::remove(path); - } - catch (const std::exception& e) - { - AICLI_LOG(CLI, Warning, << "Failed to remove installer file after execution. Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(CLI, Warning, << "Failed to remove installer file after execution. Reason unknown."); - } - } - } - - void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) - { - context << - Workflow::ReportManifestIdentity << - Workflow::ShowInstallationDisclaimer; - } - - void InstallPackageInstaller(Execution::Context& context) - { - context << - Workflow::ReportExecutionStage(ExecutionStage::Download) << - Workflow::DownloadInstaller << - Workflow::ReportExecutionStage(ExecutionStage::PreExecution) << - Workflow::SnapshotARPEntries << - Workflow::ReportExecutionStage(ExecutionStage::Execution) << - Workflow::ExecuteInstaller << - Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << - Workflow::ReportARPChanges << - Workflow::RemoveInstaller; - } - - void InstallPackageVersion(Execution::Context& context) - { - context << - Workflow::SelectInstaller << - Workflow::EnsureApplicableInstaller << - Workflow::ReportIdentityAndInstallationDisclaimer << - Workflow::GetDependenciesFromInstaller << - Workflow::ManagePackageDependencies << - Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << - Workflow::InstallPackageInstaller; - } - - void SelectInstallerMultiple(Execution::Context& context) - { - bool allSucceeded = true; - DependencyList allDependencies; - std::vector installers; - - for (auto package : context.Get()) - { - Logging::SubExecutionTelemetryScope subExecution; - - // We want to do best effort to install all packages regardless of previous failures - auto installContextPtr = context.Clone(); - Execution::Context& installContext = *installContextPtr; - - // Extract the data needed for installing - installContext.Add(package.PackageVersion); - installContext.Add(package.PackageVersion->GetManifest()); - - // TODO: In the future, it would be better to not have to convert back and forth from a string - installContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(package.PackageRequest.Scope)); - - installContext << - Workflow::SelectInstaller << - Workflow::EnsureApplicableInstaller; - - if (installContext.IsTerminated()) - { - allSucceeded = false; - continue; - } - - const auto& installer = installContext.Get(); - installers.push_back({package.PackageVersion, installer.value(), false}); - - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) - { - allDependencies.Add(installer->Dependencies); - } - } - - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) - { - context.Add(allDependencies); - context << Workflow::ReportDependencies(Resource::String::ImportCommandReportDependencies); - } - context.Add(installers); - context << InstallMultiple; - } - - void InstallMultiple(Execution::Context& context) - { - bool allSucceeded = true; - - const auto& installers = context.Get(); - - for (auto packageAndInstaller : installers) - { - auto packageVersion = packageAndInstaller.PackageVersion; - auto installer = packageAndInstaller.Installer; - - auto installContextPtr = context.Clone(); - Execution::Context& installContext = *installContextPtr; - - // set data needed for installing - installContext.Add(packageVersion); - installContext.Add(packageVersion->GetManifest()); - installContext.Add(installer); - - installContext << - ReportIdentityAndInstallationDisclaimer << - Workflow::InstallPackageInstaller; - - if (installContext.IsTerminated()) - { - if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) - { - // This means that the subcontext being terminated is due to an overall abort - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; - } - - allSucceeded = false; - } - } - - if (!allSucceeded) - { - context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED); - } - } - - void SnapshotARPEntries(Execution::Context& context) try - { - // Ensure that installer type might actually write to ARP, otherwise this is a waste of time - auto installer = context.Get(); - - if (installer && MightWriteToARP(installer->InstallerType)) - { - std::shared_ptr arpSource = context.Reporter.ExecuteWithProgress( - [](IProgressCallback& progress) - { - return Repository::OpenPredefinedSource(PredefinedSource::ARP, progress); - }, true); - - std::vector> entries; - - for (const auto& entry : arpSource->Search({}).Matches) - { - auto installed = entry.Package->GetInstalledVersion(); - if (installed) - { - entries.emplace_back(std::make_tuple( - entry.Package->GetProperty(PackageProperty::Id), - installed->GetProperty(PackageVersionProperty::Version), - installed->GetProperty(PackageVersionProperty::Channel))); - } - } - - std::sort(entries.begin(), entries.end()); - - context.Add(std::move(entries)); - } - } - CATCH_LOG() - - void ReportARPChanges(Execution::Context& context) try - { - if (context.Contains(Execution::Data::ARPSnapshot)) - { - const auto& entries = context.Get(); - - // Open it again to get the (potentially) changed ARP entries - std::shared_ptr arpSource = context.Reporter.ExecuteWithProgress( - [](IProgressCallback& progress) - { - return Repository::OpenPredefinedSource(PredefinedSource::ARP, progress); - }, true); - - std::vector changes; - - for (auto& entry : arpSource->Search({}).Matches) - { - auto installed = entry.Package->GetInstalledVersion(); - - if (installed) - { - auto entryKey = std::make_tuple( - entry.Package->GetProperty(PackageProperty::Id), - installed->GetProperty(PackageVersionProperty::Version), - installed->GetProperty(PackageVersionProperty::Channel)); - - auto itr = std::lower_bound(entries.begin(), entries.end(), entryKey); - if (itr == entries.end() || *itr != entryKey) - { - changes.emplace_back(std::move(entry)); - } - } - } - - // Also attempt to find the entry based on the manifest data - const auto& manifest = context.Get(); - - SearchRequest nameAndPublisherRequest; - - // The default localization must contain the name or we cannot do this lookup - if (manifest.DefaultLocalization.Contains(Localization::PackageName)) - { - AppInstaller::Manifest::Manifest::string_t defaultName = manifest.DefaultLocalization.Get(); - AppInstaller::Manifest::Manifest::string_t defaultPublisher; - if (manifest.DefaultLocalization.Contains(Localization::Publisher)) - { - defaultPublisher = manifest.DefaultLocalization.Get(); - } - - nameAndPublisherRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, defaultName, defaultPublisher)); - - for (const auto& loc : manifest.Localizations) - { - if (loc.Contains(Localization::PackageName) || loc.Contains(Localization::Publisher)) - { - nameAndPublisherRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, - loc.Contains(Localization::PackageName) ? loc.Get() : defaultName, - loc.Contains(Localization::Publisher) ? loc.Get() : defaultPublisher)); - } - } - } - - std::vector productCodes; - for (const auto& installer : manifest.Installers) - { - if (!installer.ProductCode.empty()) - { - if (std::find(productCodes.begin(), productCodes.end(), installer.ProductCode) == productCodes.end()) - { - nameAndPublisherRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode)); - productCodes.emplace_back(installer.ProductCode); - } - } - } - - SearchResult findByManifest; - - // Don't execute this search if it would just find everything - if (!nameAndPublisherRequest.IsForEverything()) - { - findByManifest = arpSource->Search(nameAndPublisherRequest); - } - - // Cross reference the changes with the search results - std::vector> packagesInBoth; - - for (const auto& change : changes) - { - for (const auto& byManifest : findByManifest.Matches) - { - if (change.Package->IsSame(byManifest.Package.get())) - { - packagesInBoth.emplace_back(change.Package); - break; - } - } - } - - // We now have all of the package changes; time to report them. - // The set of cases we could have for changes to ARP: - // 0 packages :: No changes were detected to ARP, which could mean that the installer - // did not write an entry. It could also be a forced reinstall. - // 1 package :: Golden path; this should be what we installed. - // 2+ packages :: We need to determine which package actually matches the one that we - // were installing. - // - // The set of cases we could have for finding packages based on the manifest: - // 0 packages :: The manifest data does not match the ARP information. - // 1 package :: Golden path; this should be what we installed. - // 2+ packages :: The data in the manifest is either too broad or we have - // a problem with our name normalization. - // - // ARP Package changes - // 0 1 N - // +------------------+--------------------+--------------------+ - // M | | | | - // a | Package does not | Manifest data does | Manifest data does | - // n 0 | write to ARP | not match ARP | not match ARP | - // i | Log this fact | Log for fixup | Log for fixup | - // f | | | | - // e +------------------+--------------------+--------------------+ - // s | | | | - // t | Reinstall of | Golden Path! | Treat manifest as | - // 1 | existing version | (assuming match) | main if common | - // r | | | | - // e +------------------+--------------------+--------------------+ - // s | | | | - // u | Not expected | Treat ARP as main | Not expected | - // l N | Log this for | | Log this for | - // t | investigation | | investigation | - // s | | | | - // +------------------+--------------------+--------------------+ - - // Find the package that we are going to log - std::shared_ptr toLog; - - // If no changes found, only log if a single matching package was found by the manifest - if (changes.empty() && findByManifest.Matches.size() == 1) - { - toLog = findByManifest.Matches[0].Package->GetInstalledVersion(); - } - // If only a single ARP entry was changed, always log that - else if (changes.size() == 1) - { - toLog = changes[0].Package->GetInstalledVersion(); - } - // Finally, if there is only a single common package, log that one - else if (packagesInBoth.size() == 1) - { - toLog = packagesInBoth[0]->GetInstalledVersion(); - } - - IPackageVersion::Metadata toLogMetadata; - if (toLog) - { - toLogMetadata = toLog->GetMetadata(); - } - - // We can only get the source identifier from an active source - std::string sourceIdentifier; - if (context.Contains(Execution::Data::PackageVersion)) - { - sourceIdentifier = context.Get()->GetProperty(PackageVersionProperty::SourceIdentifier); - } - - Logging::Telemetry().LogSuccessfulInstallARPChange( - sourceIdentifier, - manifest.Id, - manifest.Version, - manifest.Channel, - changes.size(), - findByManifest.Matches.size(), - packagesInBoth.size(), - toLog ? static_cast(toLog->GetProperty(PackageVersionProperty::Name)) : "", - toLog ? static_cast(toLog->GetProperty(PackageVersionProperty::Version)) : "", - toLog ? static_cast(toLogMetadata[PackageVersionMetadata::Publisher]) : "", - toLog ? static_cast(toLogMetadata[PackageVersionMetadata::InstalledLocale]) : "" - ); - } - } - CATCH_LOG() -} From 145c64cdff5833ba07cca40dee204dcf83b1cba5 Mon Sep 17 00:00:00 2001 From: fzanollo Date: Tue, 3 Aug 2021 17:21:41 -0300 Subject: [PATCH 37/41] Specs for show dependencies' feature #1012 (#1114) * specs for show dependencies * #163 - Dependencies with relative paths Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * Typos in show dependencies spec file Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> * change pattern for SHA256 and add new words to expect * change import output, show all dependencies together * validate will show dependencies * change multiple dependencies from oneline to nested one line each * uninstall will not be covered in this iteration, adding some details about dependencies declaration on manifest * spellcheck Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com> --- .github/actions/spelling/expect.txt | 7 +- .github/actions/spelling/patterns.txt | 2 +- doc/specs/#1012 - Show dependencies.md | 133 +++++++++++++++++++++++++ doc/specs/#163 - Dependencies.md | 4 +- 4 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 doc/specs/#1012 - Show dependencies.md diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 36cf9ff0ff..19b055e822 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -114,12 +114,13 @@ FULLMUTEX FULLWIDTH fundraiser fuzzer +fzanollo gcpi GES GESMBH GHS gity -globals +Globals helplib helplibrary hhx @@ -154,7 +155,7 @@ itr IVector IWeb IZone -JDKs +jdk jfearn JObject jp @@ -213,6 +214,7 @@ netlify Newtonsoft NOEXPAND normer +npp nsis nuffing nullopt @@ -350,4 +352,5 @@ wwinmain WZDNCRFJ XPLATSTR xsi +Zanollo zy diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt index 29c2ad3a9d..2495065cdc 100644 --- a/.github/actions/spelling/patterns.txt +++ b/.github/actions/spelling/patterns.txt @@ -3,7 +3,7 @@ WinGetDevCLI_8wekyb3d8bbwe _tisf_sqliteReturnValue microsoft\.com/[^])">]* -Sha256: [0-9A-Fa-f]{64} +S[Hh][Aa]256: [0-9A-Fa-f]{64} SHA256::ConvertToBytes\("[0-9A-Fa-f]{64}" # data urls data:[a-zA-Z=;,/0-9+-]+ diff --git a/doc/specs/#1012 - Show dependencies.md b/doc/specs/#1012 - Show dependencies.md new file mode 100644 index 0000000000..40c6416ebd --- /dev/null +++ b/doc/specs/#1012 - Show dependencies.md @@ -0,0 +1,133 @@ +--- +author: Florencia Zanollo fzanollo/t-fzanollo@microsoft.com +created on: 2021-05-28 +last updated: 2021-05-28 +issue id: 1012 +--- + +# Show dependencies + +For [#1012](https://github.com/microsoft/winget-cli/issues/1012) + +## Abstract +Several packages require other packages as dependencies. The Windows Package Manager should be able to support declared dependencies and, as a first step to manage them, inform the user about any required ones. + +## Solution Design +The Windows Package Manager should be able to report package dependency information for each of the four different types of dependencies declared in the [v1.0 manifest schemas](https://github.com/microsoft/winget-cli/blob/master/schemas/JSON/manifests/v1.0.0/). + +* Windows Features +* Windows Libraries +* Package Dependencies (same source) +* External Dependencies + +Only dependencies declared in the manifest/installer will be shown, not the entire dependency graph. + +### install +The install command will enumerate the dependencies of the package version being installed as follows: +``` +> winget install Notepad++ +Found Notepad++ [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +This package requires the following dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe +Successfully verified installer hash +Starting package install... +``` + +### show +The show command will enumerate the dependencies of the package as follows: +``` +> winget show Notepad++ +Found Notepad++ [Notepad++.Notepad++] +Version: 7.9.5 +Publisher: Notepad++ Team +Author: Don Ho +Moniker: notepad++ +Description: Notepad++ is a free (as in “free speech” and also as in “free beer”) source code editor and Notepad replacement that supports several languages. Running in the MS Windows environment, its use is governed by GNU General Public License. +Homepage: https://notepad-plus-plus.org/ +License: GPL-2.0-only +License Url: https://raw.githubusercontent.com/notepad-plus-plus/notepad-plus-plus/v7.9.5/LICENSE +Installer: + Type: Nullsoft + Locale: en-US + Download Url: https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe + SHA256: 4881548cd86491b453520e83c19292c93b9c6ce485a1f9eb9301e3913a9baced + Dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +``` + +### upgrade +The upgrade command will enumerate the dependencies of the package as follows: +``` +> winget upgrade Notepad++ +Found Notepad++ [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +This package requires the following dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Successfully verified installer hash +Starting package install... +``` +As of now, it will not try to validate nor install any of the dependencies for any package version. + +### uninstall +Uninstall needs more work as we don't have the actual installer to get the dependencies from. This will not be added in this step. + +### validate +Will gather and report dependencies for all of the installers found. Will not check if they are valid nor if there are duplicates. +``` +Manifest has the following dependencies that were not validated; ensure that they are valid: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Manifest validation succeeded. +``` + +### import +Will gather all the dependencies from the packages included in the import and show them together before starting. +``` +The packages found in this import have the following dependencies: + - Windows Feature: + Hyper-V + Containers + - Windows Libraries: + Microsoft.WinJS + - Package: + Microsoft.WindowsTerminal + - External: + JDK-11.0.10 +Found [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8/npp.8.0.Installer.x64.exe +Successfully verified installer hash +Starting package install... +Successfully installed +Found [plex.Plex] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Successfully verified installer hash +Starting package install... +Successfully installed +``` + +## Capabilities +It's only an informational feature, will not check if the dependency is a valid one, nor if the source is available. +If a dependency is declared more than once (for example when gathering all dependencies in an import) it will only show the highest minimum version needed. + +Keep in mind dependencies can be declared on the root manifest and on each of the installers. If they happen to be declared in both, installer's dependencies will override those of the manifest. With the manifest's dependencies working as a default whenever installer's dependencies are not declared. + +## Future considerations +It may be able to enable/disable this feature using extra options for the command. diff --git a/doc/specs/#163 - Dependencies.md b/doc/specs/#163 - Dependencies.md index 6927b560cf..cb510e0716 100644 --- a/doc/specs/#163 - Dependencies.md +++ b/doc/specs/#163 - Dependencies.md @@ -58,7 +58,7 @@ These include other packages. The restriction on these dependencies is that they ### External Dependencies These include dependencies from outside of the source the original package is distributed. In some cases suitable items may exist in the same source, but for licensing or personal preference, no explicit package should be required. One example is Java. Many vendors offer JREs and JDKs so it may be more reasonable to have a user informed, and allow the user to confirm the presence of a dependency. - +As a first step the Windows Package Manager should show dependency information to the user, see issue [1012](github.com/microsoft/winget-cli/issues/1012) with [spec](../../dependencies/doc/specs/%231012%20-%20Show%20dependencies.md). ## UI/UX Design @@ -118,4 +118,4 @@ The import command may be able to take advantage of determining all dependencies [Scoop Dependencies](https://scoop.netlify.app/concepts/#dependencies) -[Chocolatey Create Packages](https://docs.chocolatey.org/en-us/create/create-packages) \ No newline at end of file +[Chocolatey Create Packages](https://docs.chocolatey.org/en-us/create/create-packages) From fe8ac77f4dc38385bad0e6117cfa5dc8a86bab82 Mon Sep 17 00:00:00 2001 From: fzanollo Date: Tue, 3 Aug 2021 18:09:04 -0300 Subject: [PATCH 38/41] Show dependencies feature #1012 (#1165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * first tests for show depenencies feature * show dependencies feature for show, install, need to change message * showflow output changed * install commands shows dependencies as in specs * tests for informing dependencies on commands: show, install, upgrade, import * refactor install flow * change import command from install flow (install multiple) * import shows all dependencies together * show dependencies for validate and uninstall * tests for validate and uninstall * create show dependencies exp feature * test for validate command * put functionality under experimental feature check * enable show dep experimental feature on unit test cases * change experimental feature name * remove info stream characters on testcase * move check for exp feature inside report function, create new DependenciesFlow * DependenciesFlow header and cpp inside Workflow * –change representation of Dependency, create DependencyType and DependencyList; add Dependency to context data * dependencies context data is of type DependencyList (not optional) * fix spelling errors * ApplyTo function, can receibe a lambda function to apply on specific DpeendencyType * localize user strings, code style changes, validate report dependencies task divided, creates ValidateFlow * change uninstall flow (gets dependencies from package version), make DependencyList.dependencies private, code style * add missing report dep on upgrade * Update src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw Co-authored-by: JohnMcPMS * compare id with ICU case insensitive * move dependencies related functions to DependenciesFlow, ReportDependencies is now a WorkflowTask receiving resource string id * TODO to get dependencies on uninstall context, remove testcase for now, move dependency related function to DependenciesFlow * wfIds not temporary * DependencyList Add function checks for existence and updates min version if needed * root dependencies are used when installer are not present, otherwise installer are preferred * merge commits from master * style and details * fix merge conflicts * spellcheck exception * detail when adding dep * divide identity report and installation disclaimer from actual installation Co-authored-by: JohnMcPMS --- .github/actions/spelling/expect.txt | 2 + doc/Settings.md | 9 + .../AppInstallerCLICore.vcxproj | 2 + .../AppInstallerCLICore.vcxproj.filters | 4 + .../Commands/UninstallCommand.cpp | 3 + .../Commands/UpgradeCommand.cpp | 12 +- .../Commands/ValidateCommand.cpp | 9 +- .../ExecutionContextData.h | 7 + src/AppInstallerCLICore/ExecutionReporter.cpp | 1 + src/AppInstallerCLICore/ExecutionReporter.h | 1 + src/AppInstallerCLICore/Resources.h | 10 +- .../Workflows/DependenciesFlow.cpp | 88 ++++++++ .../Workflows/DependenciesFlow.h | 41 ++++ .../Workflows/InstallFlow.cpp | 69 +++++- .../Workflows/InstallFlow.h | 6 + .../Workflows/ShowFlow.cpp | 66 ++++-- .../Workflows/UpdateFlow.cpp | 7 +- .../Shared/Strings/en-us/winget.resw | 28 +++ .../AppInstallerCLITests.vcxproj | 21 ++ .../AppInstallerCLITests.vcxproj.filters | 21 ++ .../RestInterface_1_0.cpp | 9 +- .../ImportFile-Good-Dependencies.json | 25 +++ .../TestData/Installer_Exe_Dependencies.yaml | 21 ++ ...ller_Exe_DependenciesMultideclaration.yaml | 24 +++ .../Installer_Exe_DependenciesOnRoot.yaml | 21 ++ .../TestData/Installer_Msi_WFDependency.yaml | 19 ++ .../Manifest-Good-AllDependencyTypes.yaml | 29 +++ .../UpdateFlowTest_ExeDependencies.yaml | 26 +++ src/AppInstallerCLITests/WorkFlow.cpp | 200 ++++++++++++++++++ src/AppInstallerCLITests/YamlManifest.cpp | 21 +- .../ExperimentalFeature.cpp | 4 + .../Manifest/ManifestYamlPopulator.cpp | 34 ++- .../Public/winget/ExperimentalFeature.h | 1 + .../Public/winget/ManifestCommon.h | 121 ++++++++++- .../Public/winget/ManifestInstaller.h | 2 +- .../Public/winget/ManifestYamlPopulator.h | 7 +- .../Public/winget/UserSettings.h | 2 + src/AppInstallerCommonCore/UserSettings.cpp | 1 + .../Schema/1_0/Json/ManifestDeserializer.cpp | 36 +++- .../Schema/1_0/Json/ManifestDeserializer.h | 2 +- 40 files changed, 941 insertions(+), 71 deletions(-) create mode 100644 src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp create mode 100644 src/AppInstallerCLICore/Workflows/DependenciesFlow.h create mode 100644 src/AppInstallerCLITests/TestData/ImportFile-Good-Dependencies.json create mode 100644 src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml create mode 100644 src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml create mode 100644 src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml create mode 100644 src/AppInstallerCLITests/TestData/Installer_Msi_WFDependency.yaml create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Good-AllDependencyTypes.yaml create mode 100644 src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 19b055e822..5a3f9a92bb 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -138,6 +138,7 @@ IHelp IHost IID IInstalled +IISOn img IName inet @@ -202,6 +203,7 @@ msftrubengu MSIHASH MSIXHASH msstore +Multideclaration multimap mx mycustom diff --git a/doc/Settings.md b/doc/Settings.md index 1b0a03e01a..ab5ac6fc0f 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -146,3 +146,12 @@ Support in WinGet for packaged callers is currently implemented as an experiment "packagedAPI": true }, ``` +### Dependencies + +Experimental feature with the aim of managing dependencies, as of now it only shows package dependency information. You can enable the feature as shown below. + +```json + "experimentalFeatures": { + "dependencies": true + }, +``` diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 2b59bcc147..97a7b78633 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -249,6 +249,7 @@ + @@ -278,6 +279,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index 3b3b8cc303..a85593277f 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -155,6 +155,8 @@ Public + + Workflows Commands @@ -280,6 +282,8 @@ Source Files + + Source Files Commands diff --git a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp index df84be4b78..2f9fd4f925 100644 --- a/src/AppInstallerCLICore/Commands/UninstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UninstallCommand.cpp @@ -6,6 +6,7 @@ #include "Workflows/InstallFlow.h" #include "Workflows/CompletionFlow.h" #include "Workflows/WorkflowBase.h" +#include "Workflows/DependenciesFlow.h" #include "Resources.h" using AppInstaller::CLI::Execution::Args; @@ -128,6 +129,8 @@ namespace AppInstaller::CLI context << Workflow::GetInstalledPackageVersion << Workflow::GetUninstallInfo << + Workflow::GetDependenciesInfoForUninstall << + Workflow::ReportDependencies(Resource::String::UninstallCommandReportDependencies) << Workflow::ReportExecutionStage(ExecutionStage::Execution) << Workflow::ExecuteUninstaller << Workflow::ReportExecutionStage(ExecutionStage::PostExecution); diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 94f42929dc..7e1b066995 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -6,6 +6,7 @@ #include "Workflows/InstallFlow.h" #include "Workflows/UpdateFlow.h" #include "Workflows/WorkflowBase.h" +#include "Workflows/DependenciesFlow.h" #include "Resources.h" using namespace AppInstaller::CLI::Execution; @@ -148,7 +149,10 @@ namespace AppInstaller::CLI GetInstalledPackageVersion << EnsureUpdateVersionApplicable << SelectInstaller << - EnsureApplicableInstaller << + EnsureApplicableInstaller << + ReportIdentityAndInstallationDisclaimer << + GetDependenciesFromInstaller << + ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << InstallPackageInstaller; } else @@ -175,7 +179,11 @@ namespace AppInstaller::CLI context << SelectLatestApplicableUpdate(true); } - context << InstallPackageInstaller; + context << + ReportIdentityAndInstallationDisclaimer << + GetDependenciesFromInstaller << + ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + InstallPackageInstaller; } } } diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp index 8955e13de2..b8b470185e 100644 --- a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "ValidateCommand.h" #include "Workflows/WorkflowBase.h" +#include "Workflows/DependenciesFlow.h" #include "Resources.h" namespace AppInstaller::CLI @@ -41,7 +42,13 @@ namespace AppInstaller::CLI try { - (void)Manifest::YamlParser::CreateFromPath(inputFile, true, true); + auto manifest = Manifest::YamlParser::CreateFromPath(inputFile, true, true); + + context.Add(manifest); + context << + Workflow::GetInstallersDependenciesFromManifest << + Workflow::ReportDependencies(Resource::String::ValidateCommandReportDependencies); + context.Reporter.Info() << Resource::String::ManifestValidationSuccess << std::endl; } catch (const Manifest::ManifestException& e) diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index f6607dc470..e0d8b63d57 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -47,6 +47,7 @@ namespace AppInstaller::CLI::Execution // On import: Sources for the imported packages Sources, ARPSnapshot, + Dependencies, Max }; @@ -184,5 +185,11 @@ namespace AppInstaller::CLI::Execution // Contains the { Id, Version, Channel } using value_t = std::vector>; }; + + template <> + struct DataMapping + { + using value_t = Manifest::DependencyList; + }; } } diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 92de8cb67a..7e0421a67c 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -11,6 +11,7 @@ namespace AppInstaller::CLI::Execution const Sequence& HelpCommandEmphasis = TextFormat::Foreground::Bright; const Sequence& HelpArgumentEmphasis = TextFormat::Foreground::Bright; + const Sequence& ManifestInfoEmphasis = TextFormat::Foreground::Bright; const Sequence& NameEmphasis = TextFormat::Foreground::BrightCyan; const Sequence& IdEmphasis = TextFormat::Foreground::BrightCyan; const Sequence& UrlEmphasis = TextFormat::Foreground::BrightBlue; diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index a6446b31a6..08fba5de25 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -142,6 +142,7 @@ namespace AppInstaller::CLI::Execution // Indirection to enable change without tracking down every place extern const VirtualTerminal::Sequence& HelpCommandEmphasis; extern const VirtualTerminal::Sequence& HelpArgumentEmphasis; + extern const VirtualTerminal::Sequence& ManifestInfoEmphasis; extern const VirtualTerminal::Sequence& NameEmphasis; extern const VirtualTerminal::Sequence& IdEmphasis; extern const VirtualTerminal::Sequence& UrlEmphasis; diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index ffca60870b..fc992bc902 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -46,6 +46,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExportIncludeVersionsArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ExportSourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExternalDependencies); WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); @@ -68,6 +69,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(HelpLinkPreamble); WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandReportDependencies); WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema); @@ -82,6 +84,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore); WINGET_DEFINE_RESOURCE_STRINGID(InstallationRequiresHigherWindows); WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallAndUpgradeCommandsReportDependencies); WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); @@ -147,6 +150,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(Package); + WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesDisabled); WINGET_DEFINE_RESOURCE_STRINGID(PoliciesEnabled); @@ -170,8 +174,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningField); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningValue); WINGET_DEFINE_RESOURCE_STRINGID(ShowChannel); WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); @@ -235,6 +239,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); WINGET_DEFINE_RESOURCE_STRINGID(UninstallAbandoned); WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandReportDependencies); WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedWithCode); WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowStartingPackageUninstall); @@ -246,6 +251,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(Usage); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ValidateManifestArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(VerboseLogsArgumentDescription); @@ -255,6 +261,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeaturesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsLibrariesDependencies); WINGET_DEFINE_RESOURCE_STRINGID(WordArgumentDescription); }; diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp new file mode 100644 index 0000000000..41bb4e9663 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "DependenciesFlow.h" + +namespace AppInstaller::CLI::Workflow +{ + void ReportDependencies::operator()(Execution::Context& context) const + { + if (!Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + return; + } + auto info = context.Reporter.Info(); + + const auto& dependencies = context.Get(); + if (dependencies.HasAny()) + { + info << Resource::StringId(m_messageId) << std::endl; + + if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsFeature)) + { + info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::WindowsFeature, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsLibrary)) + { + info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::WindowsLibrary, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::Package)) + { + info << " - " << Resource::String::PackageDependencies << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::Package, [&info](Manifest::Dependency dependency) + { + info << " " << dependency.Id; + if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value() << "]"; + info << std::endl; + }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::External)) + { + info << " - " << Resource::String::ExternalDependencies << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::External, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + } + } + + void GetInstallersDependenciesFromManifest(Execution::Context& context) { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + const auto& manifest = context.Get(); + Manifest::DependencyList allDependencies; + + for (const auto& installer : manifest.Installers) + { + allDependencies.Add(installer.Dependencies); + } + + context.Add(allDependencies); + } + } + + void GetDependenciesFromInstaller(Execution::Context& context) + { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + const auto& installer = context.Get(); + if (installer) + { + context.Add(installer->Dependencies); + } + } + } + + void GetDependenciesInfoForUninstall(Execution::Context& context) + { + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + // 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 + context.Add(Manifest::DependencyList()); // sending empty list of dependencies for now + } + } +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h new file mode 100644 index 0000000000..e781411d47 --- /dev/null +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Shows information about dependencies. + // Required Args: message to use at the beginning, before outputting dependencies + // Inputs: Dependencies + // Outputs: None + struct ReportDependencies : public WorkflowTask + { + ReportDependencies(AppInstaller::StringResource::StringId messageId) : + WorkflowTask("ReportDependencies"), m_messageId(messageId) {} + + void operator()(Execution::Context& context) const override; + + private: + AppInstaller::StringResource::StringId m_messageId; + }; + + // Gathers all installers dependencies from manifest. + // Required Args: None + // Inputs: Manifest + // Outputs: Dependencies + void GetInstallersDependenciesFromManifest(Execution::Context& context); + + // Gathers package dependencies information from installer. + // Required Args: None + // Inputs: Installer + // Outputs: Dependencies + void GetDependenciesFromInstaller(Execution::Context& context); + + // TODO: + // Gathers dependencies information for the uninstall command. + // Required Args: None + // Inputs: None + // Outputs: Dependencies + void GetDependenciesInfoForUninstall(Execution::Context& context); +} \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 6d130d3c34..2f6fd43126 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -7,6 +7,7 @@ #include "ShellExecuteInstallerHandler.h" #include "MSStoreInstallerHandler.h" #include "WorkflowBase.h" +#include "Workflows/DependenciesFlow.h" namespace AppInstaller::CLI::Workflow { @@ -387,11 +388,16 @@ namespace AppInstaller::CLI::Workflow } } - void InstallPackageInstaller(Execution::Context& context) + void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) { context << Workflow::ReportManifestIdentity << - Workflow::ShowInstallationDisclaimer << + Workflow::ShowInstallationDisclaimer; + } + + void InstallPackageInstaller(Execution::Context& context) + { + context << Workflow::ReportExecutionStage(ExecutionStage::Download) << Workflow::DownloadInstaller << Workflow::ReportExecutionStage(ExecutionStage::PreExecution) << @@ -408,12 +414,27 @@ namespace AppInstaller::CLI::Workflow context << Workflow::SelectInstaller << Workflow::EnsureApplicableInstaller << + Workflow::ReportIdentityAndInstallationDisclaimer << + Workflow::GetDependenciesFromInstaller << + Workflow::ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << Workflow::InstallPackageInstaller; } + const struct PackagesAndInstallers + { + PackagesAndInstallers(std::optional inst, + AppInstaller::CLI::Execution::PackageToInstall pkg) : Installer(inst), Package(pkg) {} + + std::optional Installer; + AppInstaller::CLI::Execution::PackageToInstall Package; + }; + void InstallMultiple(Execution::Context& context) { bool allSucceeded = true; + DependencyList allDependencies; + std::vector installers; + for (auto package : context.Get()) { Logging::SubExecutionTelemetryScope subExecution; @@ -429,7 +450,49 @@ namespace AppInstaller::CLI::Workflow // TODO: In the future, it would be better to not have to convert back and forth from a string installContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(package.PackageRequest.Scope)); - installContext << InstallPackageVersion; + installContext << + Workflow::SelectInstaller << + Workflow::EnsureApplicableInstaller; + + if (installContext.IsTerminated()) + { + allSucceeded = false; + continue; + } + + const auto& installer = installContext.Get(); + installers.push_back(PackagesAndInstallers(installer, package)); + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + if (installer) allDependencies.Add(installer->Dependencies); + } + } + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + context.Add(allDependencies); + context << Workflow::ReportDependencies(Resource::String::ImportCommandReportDependencies); + } + + for (auto packageAndInstaller : installers) + { + auto package = packageAndInstaller.Package; + auto installer = packageAndInstaller.Installer; + + auto installContextPtr = context.Clone(); + Execution::Context& installContext = *installContextPtr; + + // set data needed for installing + installContext.Add(package.PackageVersion); + installContext.Add(package.PackageVersion->GetManifest()); + installContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(package.PackageRequest.Scope)); + installContext.Add(installer); + + installContext << + ReportIdentityAndInstallationDisclaimer << + Workflow::InstallPackageInstaller; + if (installContext.IsTerminated()) { if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 30837e856c..ac989be7c2 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -77,6 +77,12 @@ namespace AppInstaller::CLI::Workflow // Outputs: None void RemoveInstaller(Execution::Context& context); + // Reports manifest identity and shows installation disclaimer + // Required Args: None + // Inputs: Manifest + // Outputs: None + void ReportIdentityAndInstallationDisclaimer(Execution::Context& context); + // Installs a specific package installer. // Required Args: None // Inputs: Manifest, Installer diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index c6da146fd0..e57da40893 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -16,16 +16,16 @@ namespace AppInstaller::CLI::Workflow const auto& installer = context.Get(); // TODO: Come up with a prettier format - context.Reporter.Info() << "Version: " << manifest.Version << std::endl; - context.Reporter.Info() << "Publisher: " << manifest.CurrentLocalization.Get() << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "PackageVersion: " << manifest.Version << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "Publisher: " << manifest.CurrentLocalization.Get() << std::endl; auto author = manifest.CurrentLocalization.Get(); if (!author.empty()) { - context.Reporter.Info() << "Author: " << author << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "Author: " << author << std::endl; } if (!manifest.Moniker.empty()) { - context.Reporter.Info() << "Moniker: " << manifest.Moniker << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "Moniker: " << manifest.Moniker << std::endl; } auto description = manifest.CurrentLocalization.Get(); if (description.empty()) @@ -35,39 +35,77 @@ namespace AppInstaller::CLI::Workflow } if (!description.empty()) { - context.Reporter.Info() << "Description: " << description << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "Description: " << description << std::endl; } auto homepage = manifest.CurrentLocalization.Get(); if (!homepage.empty()) { - context.Reporter.Info() << "Homepage: " << homepage << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "PackageUrl: " << homepage << std::endl; } - context.Reporter.Info() << "License: " << manifest.CurrentLocalization.Get() << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "License: " << manifest.CurrentLocalization.Get() << std::endl; auto licenseUrl = manifest.CurrentLocalization.Get(); if (!licenseUrl.empty()) { - context.Reporter.Info() << "License Url: " << licenseUrl << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "LicenseUrl: " << licenseUrl << std::endl; } - context.Reporter.Info() << "Installer:" << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << "Installer:" << std::endl; if (installer) { - context.Reporter.Info() << " Type: " << Manifest::InstallerTypeToString(installer->InstallerType) << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << " InstallerType: " << Manifest::InstallerTypeToString(installer->InstallerType) << std::endl; if (!installer->Locale.empty()) { - context.Reporter.Info() << " Locale: " << installer->Locale << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << " InstallerLocale: " << installer->Locale << std::endl; } if (!installer->Url.empty()) { - context.Reporter.Info() << " Download Url: " << installer->Url << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << " InstallerUrl: " << installer->Url << std::endl; } if (!installer->Sha256.empty()) { - context.Reporter.Info() << " SHA256: " << Utility::SHA256::ConvertToString(installer->Sha256) << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << " InstallerSha256: " << Utility::SHA256::ConvertToString(installer->Sha256) << std::endl; } if (!installer->ProductId.empty()) { - context.Reporter.Info() << " Store Product Id: " << installer->ProductId << std::endl; + context.Reporter.Info() << Execution::ManifestInfoEmphasis << " ProductId: " << installer->ProductId << std::endl; + } + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) { + auto info = context.Reporter.Info(); + const auto& dependencies = installer->Dependencies; + + if (dependencies.HasAny()) + { + info << Execution::ManifestInfoEmphasis << " Dependencies: " << std::endl; + + if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsFeature)) + { + info << " - WindowsFeatures: " << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::WindowsFeature, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsLibrary)) + { + info << " - WindowsLibraries: " << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::WindowsLibrary, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::Package)) + { + info << " - PackageDependencies: " << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::Package, [&info](Manifest::Dependency dependency) { + info << " " << dependency.Id; + if (dependency.MinVersion) info << " [>= " << dependency.MinVersion.value() << "]"; + info << std::endl; + }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::External)) + { + info << " - ExternalDependencies: " << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::External, [&info](Manifest::Dependency dependency) {info << " " << dependency.Id << std::endl; }); + } + } } } else diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 86897de875..238e724ffe 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "WorkflowBase.h" +#include "DependenciesFlow.h" #include "InstallFlow.h" #include "UpdateFlow.h" #include "ManifestComparator.h" @@ -111,7 +112,11 @@ namespace AppInstaller::CLI::Workflow updateAllFoundUpdate = true; - updateContext << InstallPackageInstaller; + updateContext << + ReportIdentityAndInstallationDisclaimer << + GetDependenciesFromInstaller << + ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + InstallPackageInstaller; updateContext.Reporter.Info() << std::endl; diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 459c90288b..9e00e20f10 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -921,4 +921,32 @@ Configuration is disabled due to Group Policy. Cancelled + + External + + + The packages found in this import have the following dependencies: + Import command sentence showed before reporting dependencies + + + This package requires the following dependencies: + Install and Upgrade commands sentence showed before reporting dependencies + + + Packages + + + This package had dependencies that may not be needed anymore: + Uninstall command sentence showed before reporting dependencies + + + Manifest has the following dependencies that were not validated; ensure that they are valid: + Validate command sentence showed before reporting dependencies + + + Windows Features + + + Windows Libraries + \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index fc094470fa..12b5b35288 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -511,6 +511,27 @@ true + + true + + + true + + + true + + + true + + + true + + + true + + + true + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 4a61cf30ce..8ecdf7e10f 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -450,5 +450,26 @@ TestData + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + \ No newline at end of file diff --git a/src/AppInstallerCLITests/RestInterface_1_0.cpp b/src/AppInstallerCLITests/RestInterface_1_0.cpp index e05b10f696..29fb5e5757 100644 --- a/src/AppInstallerCLITests/RestInterface_1_0.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_0.cpp @@ -259,11 +259,10 @@ namespace REQUIRE(actualInstaller.Commands.at(0) == "command1"); REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.WindowsFeatures.at(0) == "feature1"); - REQUIRE(actualInstaller.Dependencies.WindowsLibraries.at(0) == "library1"); - REQUIRE(actualInstaller.Dependencies.PackageDependencies.at(0).Id == "Foo.Baz"); - REQUIRE(actualInstaller.Dependencies.PackageDependencies.at(0).MinVersion == "2.0.0"); - REQUIRE(actualInstaller.Dependencies.ExternalDependencies.at(0) == "FooBarBaz"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); REQUIRE(actualInstaller.PackageFamilyName == "FooBar.PackageFamilyName"); REQUIRE(actualInstaller.ProductCode == ""); REQUIRE(actualInstaller.Capabilities.at(0) == "Bluetooth"); diff --git a/src/AppInstallerCLITests/TestData/ImportFile-Good-Dependencies.json b/src/AppInstallerCLITests/TestData/ImportFile-Good-Dependencies.json new file mode 100644 index 0000000000..caac43c1d3 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ImportFile-Good-Dependencies.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://aka.ms/winget-packages.schema.1.0.json", + "CreationDate": "2021-01-01T12:00:00.000", + "Sources": [ + { + "Packages": [ + { + "Id": "AppInstallerCliTest.TestExeInstaller.Dependencies", + "Version": "2.0.0.0" + }, + { + "Id": "AppInstallerCliTest.TestMsixInstaller.WFDep", + "Version": "1.0.0.0" + } + ], + "SourceDetails": { + "Argument": "//arg", + "Identifier": "*TestSource", + "Name": "TestSource", + "Type": "Microsoft.TestSource" + } + } + ], + "WinGetVersion": "1.0.0" +} \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml new file mode 100644 index 0000000000..9d65242fa5 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml @@ -0,0 +1,21 @@ +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Exe Installer With Package Dep +Publisher: Microsoft Corporation +Moniker: AICLITestExePackageDep +License: Test +ShortDescription: AppInstaller Test Exe Installer With Package Dep +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence + Dependencies: + WindowsFeatures: + - PreviewIIS +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml new file mode 100644 index 0000000000..dddc675865 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml @@ -0,0 +1,24 @@ +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Exe Installer With Package Dep +Publisher: Microsoft Corporation +Moniker: AICLITestExePackageDep +License: Test +ShortDescription: AppInstaller Test Exe Installer With Package Dep +Dependencies: + WindowsFeatures: + - PreviewIISOnRoot +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence + Dependencies: + WindowsFeatures: + - PreviewIIS +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml new file mode 100644 index 0000000000..f4146721d4 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml @@ -0,0 +1,21 @@ +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Exe Installer With Package Dep +Publisher: Microsoft Corporation +Moniker: AICLITestExePackageDep +License: Test +ShortDescription: AppInstaller Test Exe Installer With Package Dep +Dependencies: + WindowsFeatures: + - PreviewIISOnRoot +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Msi_WFDependency.yaml b/src/AppInstallerCLITests/TestData/Installer_Msi_WFDependency.yaml new file mode 100644 index 0000000000..acd8ea69df --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Installer_Msi_WFDependency.yaml @@ -0,0 +1,19 @@ +PackageIdentifier: AppInstallerCliTest.TestMsixInstaller.WFDep +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test MSIX Installer With Windows Feature Dep +Publisher: Microsoft Corporation +Moniker: AICLITestMsixWindowsFeatureDep +License: Test +ShortDescription: AppInstaller Test MSIX Installer With Windows Feature Dep +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe + Dependencies: + WindowsFeatures: + - Hyper-V +ManifestType: singleton +ManifestVersion: 1.0.0 \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-AllDependencyTypes.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-AllDependencyTypes.yaml new file mode 100644 index 0000000000..06cc453fb4 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-AllDependencyTypes.yaml @@ -0,0 +1,29 @@ +# Installer with all types of dependencies +PackageIdentifier: AppInstallerCliTest.TestMsixInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test MSIX Installer +ShortDescription: AppInstaller Test MSIX Installer +Publisher: Microsoft Corporation +Moniker: AICLITestMsix +License: Test +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe + Dependencies: + WindowsFeatures: + - WindowsFeaturesDep1 + - WindowsFeaturesDep2 + WindowsLibraries: + - WindowsLibrariesDep + PackageDependencies: + - PackageIdentifier: Package.Dep1-x64 + MinimumVersion: 1.0 + - PackageIdentifier: Package.Dep2-x64 + ExternalDependencies: + - ExternalDep +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml new file mode 100644 index 0000000000..25b55a6950 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml @@ -0,0 +1,26 @@ +# Same content with Installer_Exe_Dependencies but with higher version +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 2.0.0.0 +PackageName: AppInstaller Test Installer +PackageLocale: en-US +Publisher: Microsoft Corporation +ShortDescription: Upgrade exe installer with dependencies +Moniker: AICLITestExe +License: Test +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Upgrade: /upgrade +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime +ManifestType: singleton +ManifestVersion: 1.0.0 \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 2e99614cf3..e767e8725b 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using namespace winrt::Windows::Foundation; using namespace winrt::Windows::Management::Deployment; @@ -200,6 +201,38 @@ namespace PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); } + if (input == "AppInstallerCliTest.TestExeInstaller.Dependencies") + { + auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Exe_Dependencies.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExeDependencies.yaml")); + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + manifest, + TestPackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ manifest2, manifest }, + this->shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller.Dependencies"))); + } + + if (input == "AppInstallerCliTest.TestMsixInstaller.WFDep") + { + auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Msi_WFDependency.yaml")); + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + std::vector{ manifest }, + this->shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller.WFDep"))); + } + return result; } }; @@ -782,6 +815,30 @@ TEST_CASE("ShowFlow_SearchAndShowAppVersion", "[ShowFlow][workflow]") REQUIRE(showOutput.str().find(" Download Url: https://ThisIsNotUsed") == std::string::npos); } +TEST_CASE("ShowFlow_Dependencies", "[ShowFlow][workflow][dependencies]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-AllDependencyTypes.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({true}); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(showOutput.str().find("Dependencies") != std::string::npos); + REQUIRE(showOutput.str().find("WindowsFeaturesDep") != std::string::npos); + REQUIRE(showOutput.str().find("WindowsLibrariesDep") != std::string::npos); + // PackageDep1 has minimum version (1.0), PackageDep2 doesn't (shouldn't show [>=...]) + REQUIRE(showOutput.str().find("Package.Dep1-x64 [>= 1.0]") != std::string::npos); + REQUIRE(showOutput.str().find("Package.Dep2-x64") != std::string::npos); + REQUIRE(showOutput.str().find("Package.Dep2-x64 [") == std::string::npos); + REQUIRE(showOutput.str().find("ExternalDep") != std::string::npos); +} + TEST_CASE("UpdateFlow_UpdateWithManifest", "[UpdateFlow][workflow]") { TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); @@ -1036,6 +1093,31 @@ TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); } +TEST_CASE("UpdateFlow_Dependencies", "[UpdateFlow][workflow][dependencies]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + OverrideForCompositeInstalledSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.TestExeInstaller.Dependencies"sv); + + TestUserSettings settings; + settings.Set({ true }); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + std::string updateResultStr = updateOutput.str(); + + // Verify dependencies are informed + REQUIRE(updateResultStr.find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); + REQUIRE(updateResultStr.find("PreviewIIS") != std::string::npos); + REQUIRE(updateResultStr.find("Preview VC Runtime") != std::string::npos); +} + TEST_CASE("UninstallFlow_UninstallExe", "[UninstallFlow][workflow]") { TestCommon::TempFile uninstallResultPath("TestExeUninstalled.txt"); @@ -1381,6 +1463,32 @@ TEST_CASE("ImportFlow_MachineScope", "[ImportFlow][workflow]") REQUIRE(installResultStr.find("/scope=machine") != std::string::npos); } +TEST_CASE("ImportFlow_Dependencies", "[ImportFlow][workflow][dependencies]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + OverrideForImportSource(context); + OverrideForMSIX(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-Dependencies.json").GetPath().string()); + + TestUserSettings settings; + settings.Set({ true }); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify dependencies for all packages are informed + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportCommandReportDependencies).get()) != std::string::npos); + REQUIRE(importOutput.str().find("PreviewIIS") != std::string::npos); + REQUIRE(importOutput.str().find("Preview VC Runtime") != std::string::npos); + REQUIRE(importOutput.str().find("Hyper-V") != std::string::npos); +} + void VerifyMotw(const std::filesystem::path& testFile, DWORD zone) { std::filesystem::path motwFile(testFile); @@ -1520,4 +1628,96 @@ TEST_CASE("InstallFlowMultiLocale_PreferenceWithBetterLocale", "[InstallFlow][wo std::string installResultStr; std::getline(installResultFile, installResultStr); REQUIRE(installResultStr.find("/en-GB") != std::string::npos); +} + +TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); +} + +TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") +{ + std::ostringstream validateOutput; + TestContext context{ validateOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::ValidateManifest, TestDataFile("Manifest-Good-AllDependencyTypes.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ true }); + + ValidateCommand validate({}); + validate.Execute(context); + INFO(validateOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(validateOutput.str().find(Resource::LocString(Resource::String::ValidateCommandReportDependencies).get()) != std::string::npos); + REQUIRE(validateOutput.str().find("WindowsFeaturesDep") != std::string::npos); + REQUIRE(validateOutput.str().find("WindowsLibrariesDep") != std::string::npos); + // PackageDep1 has minimum version (1.0), PackageDep2 doesn't (shouldn't show [>=...]) + REQUIRE(validateOutput.str().find("Package.Dep1-x64 [>= 1.0]") != std::string::npos); + REQUIRE(validateOutput.str().find("Package.Dep2-x64") != std::string::npos); + REQUIRE(validateOutput.str().find("Package.Dep2-x64 [") == std::string::npos); + REQUIRE(validateOutput.str().find("ExternalDep") != std::string::npos); +} + +TEST_CASE("DependenciesMultideclaration_InstallerDependenciesPreference", "[dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesMultideclaration.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify installer dependencies are shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); + // and root dependencies are not + REQUIRE(installOutput.str().find("PreviewIISOnRoot") == std::string::npos); +} + +TEST_CASE("InstallerWithoutDependencies_RootDependenciesAreUsed", "[dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesOnRoot.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify root dependencies are shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallAndUpgradeCommandsReportDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIISOnRoot") != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index ed67a8b27e..d5cbab39fa 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -382,12 +382,11 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); auto dependencies = manifest.DefaultInstallerInfo.Dependencies; - REQUIRE(dependencies.WindowsFeatures == MultiValue{ "IIS" }); - REQUIRE(dependencies.WindowsLibraries == MultiValue{ "VC Runtime" }); - REQUIRE(dependencies.PackageDependencies.size() == 1); - REQUIRE(dependencies.PackageDependencies[0].Id == "Microsoft.MsixSdkDep"); - REQUIRE(dependencies.PackageDependencies[0].MinVersion == "1.0.0"); - REQUIRE(dependencies.ExternalDependencies == MultiValue{ "Outside dependencies" }); + REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsFeature, "IIS")); + REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsLibrary, "VC Runtime")); + REQUIRE(dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDep", "1.0.0")); + REQUIRE(dependencies.HasExactDependency(DependencyType::External, "Outside dependencies")); + REQUIRE(dependencies.Size() == 4); REQUIRE(manifest.DefaultInstallerInfo.Capabilities == MultiValue{ "internetClient" }); REQUIRE(manifest.DefaultInstallerInfo.RestrictedCapabilities == MultiValue{ "runFullTrust" }); @@ -430,11 +429,11 @@ void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton) REQUIRE(installer1.FileExtensions == MultiValue{ "appxbundle", "msixbundle", "appx", "msix" }); auto installer1Dependencies = installer1.Dependencies; - REQUIRE(installer1Dependencies.WindowsFeatures == MultiValue{ "PreviewIIS" }); - REQUIRE(installer1Dependencies.WindowsLibraries == MultiValue{ "Preview VC Runtime" }); - REQUIRE(installer1Dependencies.PackageDependencies.size() == 1); - REQUIRE(installer1Dependencies.PackageDependencies[0].Id == "Microsoft.MsixSdkDepPreview"); - REQUIRE(installer1Dependencies.ExternalDependencies == MultiValue{ "Preview Outside dependencies" }); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsFeature, "PreviewIIS")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "Preview VC Runtime")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDepPreview")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::External, "Preview Outside dependencies")); + REQUIRE(installer1Dependencies.Size() == 4); REQUIRE(installer1.Capabilities == MultiValue{ "internetClientPreview" }); REQUIRE(installer1.RestrictedCapabilities == MultiValue{ "runFullTrustPreview" }); diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp index 53039171af..323e2c88fd 100644 --- a/src/AppInstallerCommonCore/ExperimentalFeature.cpp +++ b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -46,6 +46,8 @@ namespace AppInstaller::Settings return userSettings.Get(); case ExperimentalFeature::Feature::PackagedAPI: return userSettings.Get(); + case ExperimentalFeature::Feature::Dependencies: + return userSettings.Get(); default: THROW_HR(E_UNEXPECTED); } @@ -76,6 +78,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Microsoft Store Support", "experimentalMSStore", "https://aka.ms/winget-settings", Feature::ExperimentalMSStore }; case Feature::PackagedAPI: return ExperimentalFeature{ "Packaged API Support", "packagedAPI", "https://aka.ms/winget-settings", Feature::PackagedAPI }; + case Feature::Dependencies: + return ExperimentalFeature{ "Show Dependencies Information", "dependencies", "https://aka.ms/winget-settings", Feature::Dependencies }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 986368ad71..58b6c56cdb 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -228,7 +228,7 @@ namespace AppInstaller::Manifest { "Commands", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Commands = ProcessStringSequenceNode(value); return {}; } }, { "Protocols", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Protocols = ProcessStringSequenceNode(value); return {}; } }, { "FileExtensions", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->FileExtensions = ProcessStringSequenceNode(value); return {}; } }, - { "Dependencies", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency = &(m_p_installer->Dependencies); return ValidateAndProcessFields(value, DependenciesFieldInfos); } }, + { "Dependencies", [this](const YAML::Node& value)->ValidationErrors { m_p_dependencyList = &(m_p_installer->Dependencies); return ValidateAndProcessFields(value, DependenciesFieldInfos); } }, { "Capabilities", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->Capabilities = ProcessStringSequenceNode(value); return {}; } }, { "RestrictedCapabilities", [this](const YAML::Node& value)->ValidationErrors { m_p_installer->RestrictedCapabilities = ProcessStringSequenceNode(value); return {}; } }, }; @@ -355,16 +355,25 @@ namespace AppInstaller::Manifest { result = { - { "WindowsFeatures", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency->WindowsFeatures = ProcessStringSequenceNode(value); return {}; } }, - { "WindowsLibraries", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency->WindowsLibraries = ProcessStringSequenceNode(value); return {}; } }, - { "PackageDependencies", [this](const YAML::Node& value)->ValidationErrors { return ProcessPackageDependenciesNode(value, m_p_dependency->PackageDependencies); } }, - { "ExternalDependencies", [this](const YAML::Node& value)->ValidationErrors { m_p_dependency->ExternalDependencies = ProcessStringSequenceNode(value); return {}; } }, + { "WindowsFeatures", [this](const YAML::Node& value)->ValidationErrors { ProcessDependenciesNode(DependencyType::WindowsFeature, value); return {}; } }, + { "WindowsLibraries", [this](const YAML::Node& value)->ValidationErrors { ProcessDependenciesNode(DependencyType::WindowsLibrary, value); return {}; } }, + { "PackageDependencies", [this](const YAML::Node& value)->ValidationErrors { ProcessPackageDependenciesNode(value); return {}; } }, + { "ExternalDependencies", [this](const YAML::Node& value)->ValidationErrors { ProcessDependenciesNode(DependencyType::External, value); return {}; } }, }; } return result; } + void ManifestYamlPopulator::ProcessDependenciesNode(DependencyType type, const YAML::Node& node) + { + const auto& ids = ProcessStringSequenceNode(node); + for (auto id : ids) + { + m_p_dependencyList->Add(Dependency(type, id)); + } + } + std::vector ManifestYamlPopulator::GetPackageDependenciesFieldProcessInfo(const ManifestVer& manifestVersion) { std::vector result = {}; @@ -450,18 +459,17 @@ namespace AppInstaller::Manifest return resultErrors; } - ValidationErrors ManifestYamlPopulator::ProcessPackageDependenciesNode(const YAML::Node& rootNode, std::vector& packageDependencies) + ValidationErrors ManifestYamlPopulator::ProcessPackageDependenciesNode(const YAML::Node& rootNode) { ValidationErrors resultErrors; - packageDependencies.clear(); for (auto const& entry : rootNode.Sequence()) { - PackageDependency packageDependency; + Dependency packageDependency = Dependency(DependencyType::Package); m_p_packageDependency = &packageDependency; auto errors = ValidateAndProcessFields(entry, PackageDependenciesFieldInfos); std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - packageDependencies.emplace_back(std::move(std::move(packageDependency))); + m_p_dependencyList->Add(std::move(std::move(packageDependency))); } return resultErrors; @@ -502,6 +510,8 @@ namespace AppInstaller::Manifest // Clear these defaults as PackageFamilyName and ProductCode needs to be copied based on InstallerType installer.PackageFamilyName.clear(); installer.ProductCode.clear(); + // Clear dependencies as installer overrides root dependencies + installer.Dependencies.Clear(); m_p_installer = &installer; auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos); @@ -518,6 +528,12 @@ namespace AppInstaller::Manifest installer.ProductCode = manifest.DefaultInstallerInfo.ProductCode; } + // If there are no dependencies on installer use default ones + if (!installer.Dependencies.HasAny()) + { + installer.Dependencies = manifest.DefaultInstallerInfo.Dependencies; + } + // Populate installer default switches if not exists auto defaultSwitches = GetDefaultKnownSwitches(installer.InstallerType); for (auto const& defaultSwitch : defaultSwitches) diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h index a6a3235303..64e44dadbc 100644 --- a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h +++ b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -22,6 +22,7 @@ namespace AppInstaller::Settings None = 0x0, ExperimentalMSStore = 0x1, PackagedAPI = 0x2, + Dependencies = 0x4, Max, // This MUST always be after all experimental features // Features listed after Max will not be shown with the features command diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 54d3993ac4..49153c0601 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -3,7 +3,7 @@ #pragma once #include #include - +#include #include #include @@ -112,20 +112,125 @@ namespace AppInstaller::Manifest Preview, }; - struct PackageDependency + enum class DependencyType { - string_t Id; - string_t MinVersion; + WindowsFeature, + WindowsLibrary, + Package, + External }; struct Dependency { - std::vector WindowsFeatures; - std::vector WindowsLibraries; - std::vector PackageDependencies; - std::vector ExternalDependencies; + DependencyType Type; + string_t Id; + std::optional MinVersion; + + Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), Id(std::move(id)), MinVersion(std::move(minVersion)) {} + Dependency(DependencyType type, string_t id) : Type(std::move(type)), Id(std::move(id)) {} + Dependency(DependencyType type) : Type(type) {} }; + struct DependencyList + { + DependencyList() = default; + + void Add(const Dependency& newDependency) + { + Dependency* existingDependency = this->HasDependency(newDependency); + + if (existingDependency != NULL) { + if (newDependency.MinVersion) + { + if (existingDependency->MinVersion) + { + const auto& newDependencyVersion = AppInstaller::Utility::Version(newDependency.MinVersion.value()); + const auto& existingDependencyVersion = AppInstaller::Utility::Version(existingDependency->MinVersion.value()); + if (newDependencyVersion > existingDependencyVersion) + { + existingDependency->MinVersion.value() = newDependencyVersion.ToString(); + } + } + else + { + existingDependency->MinVersion.value() = newDependency.MinVersion.value(); + } + } + } + else + { + dependencies.push_back(newDependency); + } + } + + void Add(const DependencyList& otherDependencyList) + { + for (const auto& dependency : otherDependencyList.dependencies) + { + this->Add(dependency); + } + } + + bool HasAny() const { return !dependencies.empty(); } + bool HasAnyOf(DependencyType type) const + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type) return true; + }; + return false; + } + + Dependency* HasDependency(const Dependency& dependencyToSearch) + { + for (auto& dependency : dependencies) { + if (dependency.Type == dependencyToSearch.Type && ICUCaseInsensitiveEquals(dependency.Id, dependencyToSearch.Id)) + { + return &dependency; + } + } + return nullptr; + } + + // for testing purposes + bool HasExactDependency(DependencyType type, string_t id, string_t minVersion = "") + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id, id)) + { + if (dependency.MinVersion) { + if (dependency.MinVersion.value() == minVersion) + { + return true; + } + } + else { + return true; + } + } + } + return false; + } + + size_t Size() + { + return dependencies.size(); + } + + void ApplyToType(DependencyType type, std::function func) const + { + for (const auto& dependency : dependencies) + { + if (dependency.Type == type) func(dependency); + } + } + + void Clear() { dependencies.clear(); } + + private: + std::vector dependencies; + }; InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 7696fd1d35..8579296c37 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -71,6 +71,6 @@ namespace AppInstaller::Manifest // For msix only std::vector RestrictedCapabilities; - Dependency Dependencies; + DependencyList Dependencies; }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index 886cf2184c..59c3a4872d 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -37,8 +37,8 @@ namespace AppInstaller::Manifest AppInstaller::Manifest::Manifest* m_p_manifest = nullptr; AppInstaller::Manifest::ManifestInstaller* m_p_installer = nullptr; std::map* m_p_switches = nullptr; - AppInstaller::Manifest::Dependency* m_p_dependency = nullptr; - AppInstaller::Manifest::PackageDependency* m_p_packageDependency = nullptr; + AppInstaller::Manifest::DependencyList* m_p_dependencyList = nullptr; + AppInstaller::Manifest::Dependency* m_p_packageDependency = nullptr; AppInstaller::Manifest::ManifestLocalization* m_p_localization = nullptr; // Cache of Installers node and Localization node @@ -60,7 +60,8 @@ namespace AppInstaller::Manifest const YAML::Node& rootNode, const std::vector& fieldInfos); - std::vector ProcessPackageDependenciesNode(const YAML::Node& rootNode, std::vector& packageDependencies); + void ProcessDependenciesNode(DependencyType type, const YAML::Node& rootNode); + std::vector ProcessPackageDependenciesNode(const YAML::Node& rootNode); std::vector PopulateManifestInternal(const YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, bool fullValidation); }; diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index fd0a46becd..455e6a5026 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -68,6 +68,7 @@ namespace AppInstaller::Settings EFExperimentalCmd, EFExperimentalArg, EFExperimentalMSStore, + EFDependencies, TelemetryDisable, InstallScopePreference, InstallScopeRequirement, @@ -114,6 +115,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalMSStore, bool, bool, false, ".experimentalFeatures.experimentalMSStore"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFDependencies, bool, bool, false, ".experimentalFeatures.dependencies"sv); SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopePreference, std::string, ScopePreference, ScopePreference::User, ".installBehavior.preferences.scope"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopeRequirement, std::string, ScopePreference, ScopePreference::None, ".installBehavior.requirements.scope"sv); diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index ff9ba61d78..0025663598 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -225,6 +225,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) WINGET_VALIDATE_PASS_THROUGH(EFExperimentalMSStore) + WINGET_VALIDATE_PASS_THROUGH(EFDependencies) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(EFPackagedAPI) diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.cpp index 6a7120c734..5e9fd361c4 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.cpp @@ -413,10 +413,10 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json JsonHelper::GetJsonValueFromNode(installerJsonObject, JsonHelper::GetUtilityString(Dependencies)); if (dependenciesObject) { - std::optional dependency = DeserializeDependency(dependenciesObject.value().get()); - if (dependency) + std::optional dependencyList = DeserializeDependency(dependenciesObject.value().get()); + if (dependencyList) { - installer.Dependencies = std::move(dependency.value()); + installer.Dependencies = std::move(dependencyList.value()); } } @@ -428,18 +428,32 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json return installer; } - std::optional ManifestDeserializer::DeserializeDependency(const web::json::value& dependenciesObject) const + std::optional ManifestDeserializer::DeserializeDependency(const web::json::value& dependenciesObject) const { if (dependenciesObject.is_null()) { return {}; } - Manifest::Dependency dependency; + Manifest::DependencyList dependencyList; - dependency.WindowsFeatures = ConvertToManifestStringArray(JsonHelper::GetRawStringArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(WindowsFeatures))); - dependency.WindowsLibraries = ConvertToManifestStringArray(JsonHelper::GetRawStringArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(WindowsLibraries))); - dependency.ExternalDependencies = ConvertToManifestStringArray(JsonHelper::GetRawStringArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(ExternalDependencies))); + auto wfIds = ConvertToManifestStringArray(JsonHelper::GetRawStringArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(WindowsFeatures))); + for (auto&& id : wfIds) + { + dependencyList.Add(Dependency(DependencyType::WindowsFeature, std::move(id))); + }; + + const auto& wlIds = ConvertToManifestStringArray(JsonHelper::GetRawStringArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(WindowsLibraries))); + for (auto id : wlIds) + { + dependencyList.Add(Dependency(DependencyType::WindowsLibrary, id)); + }; + + const auto& extIds = ConvertToManifestStringArray(JsonHelper::GetRawStringArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(ExternalDependencies))); + for (auto id : extIds) + { + dependencyList.Add(Dependency(DependencyType::External, id)); + }; // Package Dependencies std::optional> packageDependencies = JsonHelper::GetRawJsonArrayFromJsonNode(dependenciesObject, JsonHelper::GetUtilityString(PackageDependencies)); @@ -450,12 +464,12 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json std::optional id = JsonHelper::GetRawStringValueFromJsonNode(packageDependency, JsonHelper::GetUtilityString(PackageIdentifier)); if (id) { - PackageDependency pkg{ std::move(id.value()) , JsonHelper::GetRawStringValueFromJsonNode(packageDependency, JsonHelper::GetUtilityString(MinimumVersion)).value_or("") }; - dependency.PackageDependencies.emplace_back(std::move(pkg)); + Dependency pkg{ DependencyType::Package, std::move(id.value()) , JsonHelper::GetRawStringValueFromJsonNode(packageDependency, JsonHelper::GetUtilityString(MinimumVersion)).value_or("") }; + dependencyList.Add(std::move(pkg)); } } } - return dependency; + return dependencyList; } } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h index f3d6de7cc0..a61b6b6f65 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h @@ -19,6 +19,6 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const; - std::optional DeserializeDependency(const web::json::value& dependenciesJsonObject) const; + std::optional DeserializeDependency(const web::json::value& dependenciesJsonObject) const; }; } From 40abbdae678b5d34280ed80ab775f446c751c0f9 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Thu, 5 Aug 2021 15:09:48 -0300 Subject: [PATCH 39/41] more testing for dependencies on install command --- .../Commands/UpgradeCommand.cpp | 4 +- .../Workflows/UpdateFlow.cpp | 1 + src/AppInstallerCLITests/WorkFlow.cpp | 129 ++++++++++++++++-- 3 files changed, 120 insertions(+), 14 deletions(-) diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index eb281e4b4f..3ed208ed52 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -149,10 +149,11 @@ namespace AppInstaller::CLI GetInstalledPackageVersion << EnsureUpdateVersionApplicable << SelectInstaller << - EnsureApplicableInstaller << + EnsureApplicableInstaller << ReportIdentityAndInstallationDisclaimer << GetDependenciesFromInstaller << ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + ManagePackageDependencies << InstallPackageInstaller; } else @@ -183,6 +184,7 @@ namespace AppInstaller::CLI ReportIdentityAndInstallationDisclaimer << GetDependenciesFromInstaller << ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + ManagePackageDependencies << InstallPackageInstaller; } } diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index 238e724ffe..55cf051e34 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -116,6 +116,7 @@ namespace AppInstaller::CLI::Workflow ReportIdentityAndInstallationDisclaimer << GetDependenciesFromInstaller << ReportDependencies(Resource::String::InstallAndUpgradeCommandsReportDependencies) << + ManagePackageDependencies << InstallPackageInstaller; updateContext.Reporter.Info() << std::endl; diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index 4d61222ee6..a478f48596 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -262,11 +262,11 @@ namespace auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Exe_Dependencies.yaml")); manifest.Id = input; manifest.Moniker = input; - // TODO maybe change package name on default locale for better debugging auto& installer = manifest.Installers.at(0); installer.ProductId = input; installer.Dependencies.Clear(); + installer.Switches[InstallerSwitchType::Custom] = input; /* * Dependencies: @@ -278,8 +278,12 @@ namespace * F: B * G: C * H: G, B + * + * installed1 */ + bool installed = false; + //-- predefined if (input == "C") { @@ -306,6 +310,12 @@ namespace installer.Dependencies.Add(Dependency(DependencyType::Package, "G")); installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); } + if (input == "installed1") + { + installed = true; + installer.Dependencies.Add(Dependency(DependencyType::Package, "G")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); + } // depends on test if (input == "StackOrderIsOk") @@ -331,18 +341,39 @@ namespace installer.Dependencies.Add(Dependency(DependencyType::Package, "C")); installer.Dependencies.Add(Dependency(DependencyType::Package, "H")); } + if (input == "DependenciesInstalled") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "installed1")); + } + //TODO: - // test for installed packages (or the ones that need upgrade) + // test for installed packages and packages that need upgrades // test for different min Version of dependencies + if (installed) + { + //auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + manifest, + TestPackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest }, + const_cast(this)->shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); + } + else + { + result.Matches.emplace_back( + ResultMatch( + TestPackage::Make( + std::vector{ manifest }, + const_cast(this)->shared_from_this() + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); + } - result.Matches.emplace_back( - ResultMatch( - TestPackage::Make( - std::vector{ manifest }, - const_cast(this)->shared_from_this() - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id))); return result; } }; @@ -514,6 +545,24 @@ void OverrideForShellExecute(TestContext& context) OverrideForUpdateInstallerMotw(context); } +void OverrideForShellExecute(TestContext& context, std::vector& installationLog) +{ + context.Override({ DownloadInstallerFile, [&installationLog](TestContext& context) + { + context.Add({ {}, {} }); + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + + auto dependency = Dependency(DependencyType::Package, context.Get().Id, context.Get().Version); + installationLog.push_back(dependency); + } }); + + context.Override({ RenameDownloadedInstaller, [](TestContext&) + { + } }); + + OverrideForUpdateInstallerMotw(context); +} + void OverrideForExeUninstall(TestContext& context) { context.Override({ ShellExecuteUninstallImpl, [](TestContext& context) @@ -1808,11 +1857,12 @@ TEST_CASE("DependencyGraph_Loop", "[InstallFlow][workflow][dependencyGraph][depe TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + std::vector installationOrder; std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideOpenSourceForDependencies(context); - OverrideForShellExecute(context); + OverrideForShellExecute(context, installationOrder); context.Args.AddArg(Execution::Args::Type::Query, "DependencyAlreadyInStackButNoLoop"sv); @@ -1825,16 +1875,24 @@ TEST_CASE("DependencyGraph_InStackNoLoop", "[InstallFlow][workflow][dependencyGr REQUIRE(installOutput.str().find("has loop") == std::string::npos); REQUIRE(installOutput.str().find("order: B, C, F, DependencyAlreadyInStackButNoLoop,") != std::string::npos); + + // Verify installers are called in order + REQUIRE(installationOrder.size() == 4); + REQUIRE(installationOrder.at(0).Id == "B"); + REQUIRE(installationOrder.at(1).Id == "C"); + REQUIRE(installationOrder.at(2).Id == "F"); + REQUIRE(installationOrder.at(3).Id == "DependencyAlreadyInStackButNoLoop"); } TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + std::vector installationOrder; std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideOpenSourceForDependencies(context); - OverrideForShellExecute(context); + OverrideForShellExecute(context, installationOrder); context.Args.AddArg(Execution::Args::Type::Query, "PathBetweenBranchesButNoLoop"sv); @@ -1848,16 +1906,24 @@ TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph REQUIRE(installOutput.str().find("has loop") == std::string::npos); REQUIRE(installOutput.str().find("order: B, C, G, H, PathBetweenBranchesButNoLoop,") != std::string::npos); + // Verify installers are called in order + REQUIRE(installationOrder.size() == 5); + REQUIRE(installationOrder.at(0).Id == "B"); + REQUIRE(installationOrder.at(1).Id == "C"); + REQUIRE(installationOrder.at(2).Id == "G"); + REQUIRE(installationOrder.at(3).Id == "H"); + REQUIRE(installationOrder.at(4).Id == "PathBetweenBranchesButNoLoop"); } TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + std::vector installationOrder; std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideOpenSourceForDependencies(context); - OverrideForShellExecute(context); + OverrideForShellExecute(context, installationOrder); context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); @@ -1871,16 +1937,22 @@ TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyG REQUIRE(installOutput.str().find("has loop") == std::string::npos); REQUIRE(installOutput.str().find("order: B, C, StackOrderIsOk,") != std::string::npos); + // Verify installers are called in order + REQUIRE(installationOrder.size() == 3); + REQUIRE(installationOrder.at(0).Id == "B"); + REQUIRE(installationOrder.at(1).Id == "C"); + REQUIRE(installationOrder.at(2).Id == "StackOrderIsOk"); } TEST_CASE("DependencyGraph_BFirst", "[InstallFlow][workflow][dependencyGraph][dependencies]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + std::vector installationOrder; std::ostringstream installOutput; TestContext context{ installOutput, std::cin }; OverrideOpenSourceForDependencies(context); - OverrideForShellExecute(context); + OverrideForShellExecute(context, installationOrder); context.Args.AddArg(Execution::Args::Type::Query, "NeedsToInstallBFirst"sv); @@ -1894,6 +1966,37 @@ TEST_CASE("DependencyGraph_BFirst", "[InstallFlow][workflow][dependencyGraph][de REQUIRE(installOutput.str().find("has loop") == std::string::npos); REQUIRE(installOutput.str().find("order: B, C, NeedsToInstallBFirst,") != std::string::npos); + // Verify installers are called in order + REQUIRE(installationOrder.size() == 3); + REQUIRE(installationOrder.at(0).Id == "B"); + REQUIRE(installationOrder.at(1).Id == "C"); + REQUIRE(installationOrder.at(2).Id == "NeedsToInstallBFirst"); +} + +TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGraph][dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + std::vector installationOrder; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideOpenSourceForDependencies(context); + OverrideForShellExecute(context, installationOrder); + + context.Args.AddArg(Execution::Args::Type::Query, "DependenciesInstalled"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find("has loop") == std::string::npos); + // dependencies installed will show on the order but the installer will not be called + REQUIRE(installOutput.str().find("order: installed1, DependenciesInstalled,") != std::string::npos); + REQUIRE(installationOrder.size() == 1); + REQUIRE(installationOrder.at(0).Id == "DependenciesInstalled"); } TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") From e8b4e6aa2d01604312f431da737e444efe865a1b Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Fri, 6 Aug 2021 13:20:12 -0300 Subject: [PATCH 40/41] valid min version test --- .../Workflows/DependenciesFlow.cpp | 7 +- .../TestData/Installer_Exe_Dependencies.yaml | 2 +- src/AppInstallerCLITests/WorkFlow.cpp | 65 +++++++++++++++++-- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 6785415356..c72e9eff9c 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -112,6 +112,11 @@ namespace AppInstaller::CLI::Workflow void ManagePackageDependencies(Execution::Context& context) { + if (!Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::Dependencies)) + { + return; + } + auto info = context.Reporter.Info(); const auto& rootManifest = context.Get(); @@ -192,7 +197,7 @@ namespace AppInstaller::CLI::Workflow installer = SelectInstallerFromMetadata(context.Args, manifest, {}); } - const auto& nodeDependencies = installer->Dependencies; + auto& nodeDependencies = installer->Dependencies; idToInstallerMap[node.Id] = {latestVersion, installer.value(), isUpdate}; return nodeDependencies; diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml index 9d65242fa5..6933de81b4 100644 --- a/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml @@ -1,5 +1,5 @@ PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies -PackageVersion: 1.0.0.0 +PackageVersion: 1.0 PackageLocale: en-US PackageName: AppInstaller Test Exe Installer With Package Dep Publisher: Microsoft Corporation diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index a478f48596..db8dfbc723 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -266,7 +266,6 @@ namespace auto& installer = manifest.Installers.at(0); installer.ProductId = input; installer.Dependencies.Clear(); - installer.Switches[InstallerSwitchType::Custom] = input; /* * Dependencies: @@ -280,6 +279,10 @@ namespace * H: G, B * * installed1 + * minVersion1.0 + * minVersion1.5 + * requires1.5: minVersion1.5 + * minVersion2.0 //invalid version (not returned as result) */ bool installed = false; @@ -313,8 +316,21 @@ namespace if (input == "installed1") { installed = true; - installer.Dependencies.Add(Dependency(DependencyType::Package, "G")); - installer.Dependencies.Add(Dependency(DependencyType::Package, "B")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "installed1Dep")); + } + if (input == "minVersion1.0") + { + manifest.Id = "minVersion"; + manifest.Version = "1.0"; + } + if (input == "minVersion1.5") + { + manifest.Id = "minVersion"; + manifest.Version = "1.5"; + } + if (input == "requires1.5") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "minVersion", "1.5")); } // depends on test @@ -345,7 +361,15 @@ namespace { installer.Dependencies.Add(Dependency(DependencyType::Package, "installed1")); } - + if (input == "DependenciesValidMinVersions") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "minVersion", "1.0")); + } + if (input == "DependenciesValidMinVersionsMultiple") + { + installer.Dependencies.Add(Dependency(DependencyType::Package, "minVersion", "1.0")); + installer.Dependencies.Add(Dependency(DependencyType::Package, "requires1.5")); + } //TODO: // test for installed packages and packages that need upgrades @@ -1993,10 +2017,41 @@ TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGr INFO(installOutput.str()); REQUIRE(installOutput.str().find("has loop") == std::string::npos); - // dependencies installed will show on the order but the installer will not be called + // dependencies installed will show on the graph order but the installer will not be called REQUIRE(installOutput.str().find("order: installed1, DependenciesInstalled,") != std::string::npos); REQUIRE(installationOrder.size() == 1); REQUIRE(installationOrder.at(0).Id == "DependenciesInstalled"); + // dependencies of an installed package will not be checked nor added to the graph + REQUIRE(installOutput.str().find("installed1Dep") == std::string::npos); +} + +TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependencyGraph][dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + std::vector installationOrder; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + OverrideOpenSourceForDependencies(context); + OverrideForShellExecute(context, installationOrder); + + context.Args.AddArg(Execution::Args::Type::Query, "DependenciesValidMinVersions"sv); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find("has loop") == std::string::npos); + // dependencies installed will show on the order but the installer will not be called + REQUIRE(installOutput.str().find("order: minVersion, DependenciesValidMinVersions,") != std::string::npos); + REQUIRE(installationOrder.size() == 2); + REQUIRE(installationOrder.at(0).Id == "minVersion"); + // minVersion 1.5 is available but this requires 1.0 so that version is installed + REQUIRE(installationOrder.at(0).MinVersion.value().ToString() == "1.0"); + REQUIRE(installationOrder.at(1).Id == "DependenciesInstalled"); } TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") From cadeaa8c75d13ab33935c5b8e0ef815da497d9f2 Mon Sep 17 00:00:00 2001 From: Florencia Zanollo Date: Fri, 6 Aug 2021 13:30:52 -0300 Subject: [PATCH 41/41] typo --- src/AppInstallerCLITests/WorkFlow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index db8dfbc723..ab4e10f2ae 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -2051,7 +2051,7 @@ TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependenc REQUIRE(installationOrder.at(0).Id == "minVersion"); // minVersion 1.5 is available but this requires 1.0 so that version is installed REQUIRE(installationOrder.at(0).MinVersion.value().ToString() == "1.0"); - REQUIRE(installationOrder.at(1).Id == "DependenciesInstalled"); + REQUIRE(installationOrder.at(1).Id == "DependenciesValidMinVersions"); } TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]")