// 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; } } }