Skip to content
114 changes: 74 additions & 40 deletions src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,71 +45,105 @@ namespace AppInstaller::CLI::Workflow
m_nodePackageInstalledVersion = GetInstalledVersion(package);
std::shared_ptr<IPackageVersionCollection> 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,
// as we won't keep searching for dependencies for installed packages
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;
}
}
}