Skip to content
Open
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,13 @@ 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));
{
// Tracks components whose sub-tree has already been walked so that cyclic
// dependencies (e.g. A -> B -> A) don't cause infinite recursion and so that
// diamond-shaped graphs aren't re-walked exponentially.
var visited = new HashSet<string>();
GetPackages(condaLock).ForEach(package => RegisterPackageWithDependencies(package, null, condaLock, singleFileComponentRecorder, visited));
}

/// <summary>
/// Updates all registered packages that don't have any ancestors.
Expand Down Expand Up @@ -60,7 +66,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="visited">The set of component ids whose dependencies have already been walked.</param>
private static void RegisterPackageWithDependencies(CondaPackage package, string parentId, CondaLock condaLock, ISingleFileComponentRecorder singleFileComponentRecorder, HashSet<string> visited)
{
if (package == null)
{
Expand All @@ -69,16 +76,24 @@ private static void RegisterPackageWithDependencies(CondaPackage package, string

var component = CreateComponent(package);

//// Register the package itself.
// Register the package itself. This also records the edge from the parent,
// so it must happen every time the package is reached, regardless of cycles.
RegisterPackage(component, parentId, false, singleFileComponentRecorder);

//// 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));
// Only walk a package's dependencies once. This guards against cyclic
// dependency graphs (which would otherwise recurse forever) and avoids
// re-walking shared sub-trees.
var visitKey = $"{component.Id}:{package.Platform}";
if (!visited.Add(visitKey))
{
return;
}

foreach (var dependency in package.Dependencies?.Keys ?? Enumerable.Empty<string>())
{
var dependencyPackage = condaLock?.Package.FirstOrDefault(condaPackage => condaPackage.Name == dependency && condaPackage.Platform == package.Platform);
RegisterPackageWithDependencies(dependencyPackage, component.Id, condaLock, singleFileComponentRecorder, visited);
}
}

/// <summary>
Expand Down Expand Up @@ -122,19 +137,9 @@ private static List<CondaPackage> GetPackages(CondaLock condaLock)
/// <param name="package">The CondaPackage to convert.</param>
/// <returns>The TypedComponent.</returns>
private static TypedComponent CreateComponent(CondaPackage package)
=> IsPythonPackage(package)
? new PipComponent(package.Name, package.Version)
: new CondaComponent(package.Name, package.Version, null, package.Category, null, null, null, null);

/// <summary>
/// Checks if a package is a python package.
///
/// If the package is either managed by pip, or if it depends on python
/// it is considered a python package.
/// </summary>
/// <param name="package">The CondaPackage.</param>
/// <returns>True if the package is a python package.</returns>
private static bool IsPythonPackage(CondaPackage package)
=> package.Manager.Equals("pip", StringComparison.OrdinalIgnoreCase) ||
package.Dependencies.Keys.Any(dependency => dependency.Equals("python", StringComparison.OrdinalIgnoreCase));
{
return package.Manager.Equals("pip", StringComparison.OrdinalIgnoreCase)
? new PipComponent(package.Name, package.Version)
: new CondaComponent(package.Name, package.Version, null, package.Category, null, null, null, null);
Comment on lines 139 to +143
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public CondaLockComponentDetector(

public override IEnumerable<ComponentType> SupportedComponentTypes => [ComponentType.Conda, ComponentType.Pip];

public override int Version { get; } = 2;
public override int Version { get; } = 3;

public override IEnumerable<string> Categories => ["Python"];

Expand Down
Loading
Loading