diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp index ad24e298f5..166eb401b9 100644 --- a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp +++ b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -45,20 +45,7 @@ namespace AppInstaller::CLI::Workflow m_nodePackageInstalledVersion = GetInstalledVersion(package); std::shared_ptr availableVersions = GetAvailableVersionsForInstalledVersion(package); - if (m_context.Args.Contains(Execution::Args::Type::Force)) - { - m_nodePackageLatestVersion = availableVersions->GetLatestVersion(); - } - else - { - Pinning::PinBehavior pinBehavior = m_context.Args.Contains(Execution::Args::Type::IncludePinned) ? Pinning::PinBehavior::IncludePinned : Pinning::PinBehavior::ConsiderPins; - - Pinning::PinningData pinningData{ Pinning::PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, m_nodePackageInstalledVersion); - - m_nodePackageLatestVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); - } - + // If the package is already installed and meets the dependency's MinVersion, skip. if (m_nodePackageInstalledVersion && dependencyNode.IsVersionOk(Utility::Version(m_nodePackageInstalledVersion->GetProperty(PackageVersionProperty::Version)))) { // return empty dependency list, @@ -66,50 +53,97 @@ namespace AppInstaller::CLI::Workflow return DependencyNodeProcessorResult::Skipped; } - if (!m_nodePackageLatestVersion) + if (!availableVersions) { error << Resource::String::DependenciesFlowPackageVersionNotFound(Utility::LocIndView{ Utility::Normalize(packageId) }) << std::endl; - AICLI_LOG(CLI, Error, << "Latest available version not found for package " << packageId); + AICLI_LOG(CLI, Error, << "Available versions not found for package " << packageId); return DependencyNodeProcessorResult::Error; } - if (!dependencyNode.IsVersionOk(Utility::Version(m_nodePackageLatestVersion->GetProperty(PackageVersionProperty::Version)))) - { - error << Resource::String::DependenciesFlowNoMinVersion(Utility::LocIndView{ Utility::Normalize(packageId) }) << std::endl; - AICLI_LOG(CLI, Error, << "No suitable min version found for package " << packageId); - return DependencyNodeProcessorResult::Error; - } - - m_nodeManifest = m_nodePackageLatestVersion->GetManifest(); - m_nodeManifest.ApplyLocale(); + // Determine pin behavior: --force should ignore pin restrictions for selection purposes. + Pinning::PinBehavior pinBehavior = m_context.Args.Contains(Execution::Args::Type::IncludePinned) || m_context.Args.Contains(Execution::Args::Type::Force) + ? Pinning::PinBehavior::IncludePinned + : Pinning::PinBehavior::ConsiderPins; - if (m_nodeManifest.Installers.empty()) - { - error << Resource::String::DependenciesFlowNoInstallerFound(Utility::LocIndView{ Utility::Normalize(m_nodeManifest.Id) }) << std::endl; - AICLI_LOG(CLI, Error, << "Installer not found for manifest " << m_nodeManifest.Id << " with version" << m_nodeManifest.Version); - return DependencyNodeProcessorResult::Error; - } + Pinning::PinningData pinningData{ Pinning::PinningData::Disposition::ReadOnly }; + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, m_nodePackageInstalledVersion); + // Iterate versions from newest to oldest, looking for the first version that: + // - satisfies dependency.MinVersion + // - is not pinned (unless includePinned or force) + // - has at least one applicable installer according to ManifestComparator + bool foundCandidate = false; IPackageVersion::Metadata installationMetadata; if (m_nodePackageInstalledVersion) { installationMetadata = m_nodePackageInstalledVersion->GetMetadata(); } - ManifestComparator manifestComparator(GetManifestComparatorOptions(m_context, installationMetadata)); - auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(m_nodeManifest); + Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(m_context, installationMetadata)); + + auto versionKeys = availableVersions->GetVersionKeys(); + for (const auto& key : versionKeys) + { + auto candidateVersion = availableVersions->GetVersion(key); + if (!candidateVersion) + { + continue; + } + + // Skip pinned versions unless explicitly allowed by flags + auto pinType = evaluator.EvaluatePinType(candidateVersion); + if (pinType != Pinning::PinType::Unknown && !m_context.Args.Contains(Execution::Args::Type::IncludePinned) && !m_context.Args.Contains(Execution::Args::Type::Force)) + { + // This version is pinned and the user did not request to include pinned versions + AICLI_LOG(CLI, Info, << "Skipping pinned version " << candidateVersion->GetProperty(PackageVersionProperty::Version) << " for package " << packageId); + continue; + } + + // Check MinVersion constraint from the dependency node + Utility::Version candidateVer(candidateVersion->GetProperty(PackageVersionProperty::Version)); + if (!dependencyNode.IsVersionOk(candidateVer)) + { + // Candidate version is lower than required min version for this dependency + AICLI_LOG(CLI, Info, << "Skipping version " << candidateVer.ToString() << " because it does not meet MinVersion for dependency " << dependencyNode.Id()); + continue; + } + + // Load manifest for this version and attempt installer selection + Manifest::Manifest manifest = candidateVersion->GetManifest(); + manifest.ApplyLocale(); + + if (manifest.Installers.empty()) + { + AICLI_LOG(CLI, Info, << "No installers in manifest for " << manifest.Id << " version " << manifest.Version); + continue; + } + + auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(manifest); + + if (!installer.has_value()) + { + // No suitable installer for this manifest; keep searching older versions. + AICLI_LOG(CLI, Info, << "No suitable installer found for manifest " << manifest.Id << " version " << manifest.Version); + continue; + } + + // Found a working candidate + m_nodePackageLatestVersion = candidateVersion; + m_nodeManifest = std::move(manifest); + m_installer = installer.value(); + foundCandidate = true; + break; + } - if (!installer.has_value()) + if (!foundCandidate) { - auto manifestId = Utility::LocIndString{ Utility::Normalize(m_nodeManifest.Id) }; - auto manifestVersion = Utility::LocIndString{ m_nodeManifest.Version }; - error << Resource::String::DependenciesFlowNoSuitableInstallerFound(manifestId, manifestVersion) << std::endl; - AICLI_LOG(CLI, Error, << "No suitable installer found for manifest " << m_nodeManifest.Id << " with version " << m_nodeManifest.Version); + error << Resource::String::DependenciesFlowNoSuitableInstallerFound(Utility::LocIndView{ Utility::Normalize(packageId) }, Utility::LocIndView{ Utility::LocIndString{ /* empty version to indicate search failed across versions */ "" } }) << std::endl; + AICLI_LOG(CLI, Error, << "No suitable installer found for any available version of package " << packageId); return DependencyNodeProcessorResult::Error; } - m_installer = installer.value(); + // Extract the dependency list from the chosen installer's dependencies m_dependenciesList = m_installer.Dependencies; return DependencyNodeProcessorResult::Success; } -} +} \ No newline at end of file