Skip to content
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static class CondaDependencyResolver
/// <param name="condaLock">The full condaLock object.</param>
/// <param name="singleFileComponentRecorder">The SingleFileComponentRecorder.</param>
public static void RecordDependencyGraphFromFile(CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder)
=> GetPackages(condaLock).ForEach(package => RegisterPackageWithDependencies(package, null, condaLock, singleFileComponentRecorder));
=> GetPackages(condaLock).ForEach(package => RegisterPackageWithDependencies(package, null, condaLock, singleFileComponentRecorder, []));

/// <summary>
/// Updates all registered packages that don't have any ancestors.
Expand Down Expand Up @@ -60,7 +60,8 @@ public static void UpdateDirectlyReferencedPackages(ISingleFileComponentRecorder
/// <param name="parentId">The id of the parent package.</param>
/// <param name="condaLock">The full condaLock object.</param>
/// <param name="singleFileComponentRecorder">The SingleFileComponentRecorder.</param>
private static void RegisterPackageWithDependencies(CondaPackage package, string parentId, CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder)
/// <param name="currentPath">The component ids in the current dependency path.</param>
private static void RegisterPackageWithDependencies(CondaPackage package, string parentId, CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder, HashSet<string> currentPath)
{
if (package == null)
{
Expand All @@ -72,13 +73,22 @@ private static void RegisterPackageWithDependencies(CondaPackage package, string
//// Register the package itself.
RegisterPackage(component, parentId, false, singleFileComponentRecorder);

//// Conda lockfiles can contain dependency cycles; retain the edge above but do not traverse the same path indefinitely.
if (!currentPath.Add(component.Id))
{
return;
}

//// Register all dependencies of the package.
package.Dependencies.Keys.ToList().ForEach(dependency =>
RegisterPackageWithDependencies(
condaLock?.Package.FirstOrDefault(condaPackage => condaPackage.Name == dependency && condaPackage.Platform == package.Platform),
component.Id,
condaLock,
singleFileComponentRecorder));
singleFileComponentRecorder,
currentPath));

currentPath.Remove(component.Id);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,48 @@ public async Task CondaComponentDetector_TestCondaLockFileAsync()
detectedComponents.Should().HaveCount(4);
}

[TestMethod]
public async Task CondaComponentDetector_CircularDependenciesDoNotOverflowAsync()
{
var condaLockContent =
@"version: 1
metadata:
platforms:
- linux-64
package:
- name: python
version: 3.12.13
manager: conda
platform: linux-64
dependencies:
pip: ''
category: main
optional: false
- name: pip
version: 26.2.1
manager: conda
platform: linux-64
dependencies:
python: ''
category: main
optional: false
";

var (scanResult, componentRecorder) = await this.detectorTestUtility
.WithFile("conda-lock.yml", condaLockContent)
.ExecuteDetectorAsync();

var detectedComponents = componentRecorder.GetDetectedComponents();
var dependencyGraph = componentRecorder.GetDependencyGraphsByLocation().Values.First();
var pythonId = detectedComponents.Single(component => component.Component is CondaComponent { Name: "python" }).Component.Id;
var pipId = detectedComponents.Single(component => component.Component is PipComponent { Name: "pip" }).Component.Id;

scanResult.ResultCode.Should().Be(ProcessingResultCode.Success);
detectedComponents.Should().HaveCount(2);
dependencyGraph.GetDependenciesForComponent(pythonId).Should().Contain(pipId);
dependencyGraph.GetDependenciesForComponent(pipId).Should().Contain(pythonId);
}

private void AssertCondaLockComponentNameAndVersion(IEnumerable<DetectedComponent> detectedComponents, string name, string version)
{
detectedComponents.SingleOrDefault(c =>
Expand Down
Loading