Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
}