// Copyright Keysight Technologies 2012-2019
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at http://mozilla.org/MPL/2.0/.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace OpenTap.Package
{
/// Type of dependency issue.
public enum DependencyIssueType
{
/// No issue noted (Usually notes an installed package).
None,
/// The dependency is missing.
Missing,
/// Incompatible version installed.
[Display("Incompatible Version")]
IncompatibleVersion,
/// One of the dependencies has a issue.
[Display("Dependency Missing")]
DependencyMissing
}
///
/// Model object for a dependency issue.
///
public struct DependencyIssue
{
///
/// Issue package, eg the missing package.
///
public string PackageName { get; set; }
///
/// Expected version.
///
public VersionSpecifier ExpectedVersion { get; set; }
///
/// The version available.
///
public SemanticVersion LoadedVersion { get; set; }
/// Denotes which kind of dependency issue it is.
public DependencyIssueType IssueType { get; set; }
}
///
/// Algorithm for calculate nth degree dependency issues.
///
public class DependencyAnalyzer
{
///
/// Broken packages from the packages used to build the object.
///
public ReadOnlyCollection BrokenPackages { get; set; }
Dictionary packagesLookup;
Dictionary> dependers;
private DependencyAnalyzer() { }
///
/// Returns the issues for a given package.
///
///
///
public List GetIssues(PackageDef pkg)
{
List brokenDependencies = new List();
foreach (var dep in pkg.Dependencies)
{
if (packagesLookup.ContainsKey(dep.Name) == false)
{
brokenDependencies.Add(new DependencyIssue() { PackageName = dep.Name, ExpectedVersion = dep.Version, IssueType = DependencyIssueType.Missing });
continue;
}
var installed = packagesLookup[dep.Name];
var iv = installed.Version;
var depbase = new DependencyIssue { PackageName = dep.Name, ExpectedVersion = dep.Version, LoadedVersion = iv };
if (installed.Version == null)
{ // missing
depbase.IssueType = DependencyIssueType.Missing;
}
else if (!dep.Version.IsCompatible(iv))
{ // incompatible
depbase.IssueType = DependencyIssueType.IncompatibleVersion;
}
else if (BrokenPackages.Any(pkg2 => pkg2.Name == dep.Name))
{ // broken dependency
depbase.IssueType = DependencyIssueType.DependencyMissing;
}
else continue;
brokenDependencies.Add(depbase);
}
return brokenDependencies;
}
/// Creates a new dependency analyzer that only shows things related to important_packages.
///
///
public DependencyAnalyzer FilterRelated(List important_packages)
{
HashSet visited = new HashSet();
Stack stack = new Stack(important_packages.Select(pkg => pkg.Name));
// moving down.
while (stack.Count > 0)
{
var top = stack.Pop();
visited.Add(top);
var pkg = packagesLookup[top];
foreach (var dep in pkg.Dependencies)
{
if (visited.Contains(dep.Name) == false)
{
visited.Add(dep.Name);
stack.Push(dep.Name);
}
}
}
// moving up
HashSet visited2 = new HashSet();
Stack stack2 = new Stack(important_packages.Select(pkg => pkg.Name));
// moving down.
while (stack2.Count > 0)
{
var top = stack2.Pop();
visited2.Add(top);
var pkg = packagesLookup[top];
foreach (var dep in dependers[pkg])
{
if (visited2.Contains(dep.Name) == false)
{
visited2.Add(dep.Name);
stack2.Push(dep.Name);
}
}
}
visited = new HashSet(visited.Concat(visited2));
return new DependencyAnalyzer
{
BrokenPackages = BrokenPackages.Where(pkg => visited.Contains(pkg.Name)).ToList().AsReadOnly(),
packagesLookup = packagesLookup,
dependers = dependers
};
}
///
/// Builds a DependencyTree based on the dependencies between the packages.
///
///
///
public static DependencyAnalyzer BuildAnalyzerContext(List packages)
{
Dictionary packagesLookup = packages.GroupBy(p => p.Name).Select(p => p.First()).ToDictionary(pkg => pkg.Name);
Dictionary> dependers = packages.ToDictionary(pkg => pkg, pkg => new List());
HashSet broken_packages = new HashSet();
foreach (var package in packages)
{
foreach (var dep in package.Dependencies)
{
if (false == packagesLookup.ContainsKey(dep.Name))
{ // missing: create placeholder. null means missing.
packagesLookup[dep.Name] = new PackageDef { Dependencies = new List(), Name = dep.Name, Version = null, Files = new List() };
dependers[packagesLookup[dep.Name]] = new List();
}
var loadedpackage = packagesLookup[dep.Name];
if (false == dep.Version.IsCompatible(loadedpackage.Version) || null == loadedpackage.Version)
{
broken_packages.Add(package);
}
dependers[packagesLookup[dep.Name]].Add(package);
}
}
// find nth order broken packages
// if a dependency is broken, the depender is also broken.
Stack newBroken = new Stack(broken_packages);
while (newBroken.Count > 0)
{
var item = newBroken.Pop();
var deps = dependers[item];
foreach (var dep in deps)
{
if (!broken_packages.Contains(dep))
{
broken_packages.Add(dep);
newBroken.Push(dep);
}
}
}
DependencyAnalyzer deptree = new DependencyAnalyzer();
deptree.BrokenPackages = broken_packages.ToList().AsReadOnly();
deptree.packagesLookup = packagesLookup;
deptree.dependers = dependers;
return deptree;
}
}
}