Skip to content

Instantly share code, notes, and snippets.

@PawelGerr
Created April 12, 2024 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PawelGerr/d80dc45603cd6dd01971780fca011fa5 to your computer and use it in GitHub Desktop.
Save PawelGerr/d80dc45603cd6dd01971780fca011fa5 to your computer and use it in GitHub Desktop.
Project Dependency Graph Analyzer
using System.Diagnostics.CodeAnalysis;
using System.Xml.Linq;
using System.Xml.XPath;
const string path = @"./Thinktecture.EntityFrameworkCore.argr";
var graph = ProjectGraph.ParseGraph(path);
var sqlServerSamples = graph.Get("Thinktecture.EntityFrameworkCore.SqlServer.Samples");
var sqliteSamples = graph.Get("Thinktecture.EntityFrameworkCore.Sqlite.Samples");
var sharedDependencies = sqlServerSamples.FlattenedDependencies
.Intersect(sqliteSamples.FlattenedDependencies)
.Order()
.ToList();
Console.WriteLine();
class ProjectGraph
{
public IReadOnlyList<Module> Modules { get; }
public IReadOnlyList<Module> LeafNodes { get; }
public IReadOnlyList<Module> RootNodes { get; }
private ProjectGraph(IReadOnlyList<Module> modules)
{
Modules = modules;
LeafNodes = modules.Where(m => m.Dependencies.Count == 0).ToList();
RootNodes = modules.Except(modules.SelectMany(m => m.Dependencies).DistinctBy(m => m.Id)).ToList();
}
public static ProjectGraph ParseGraph(string path)
{
var doc = XDocument.Load(path);
var modules = doc.XPathSelectElements("//Module")
.Select(m => new Module(Guid.Parse(m.Attribute("Identifier")?.Value ?? throw new Exception("No Id")),
m.Attribute("Name")?.Value ?? throw new Exception("No Name")))
.ToDictionary(i => i.Id);
var references = doc.XPathSelectElements("//Reference")
.Select(m => new
{
ModuleId = Guid.Parse(m.Attribute("SourceId")?.Value ?? throw new Exception("No SourceId")),
PrincipalId = Guid.Parse(m.Attribute("TargetId")?.Value ?? throw new Exception("No TargetId"))
})
.ToLookup(i => i.ModuleId, i => i.PrincipalId);
foreach (var (id, module) in modules)
{
module.Dependencies = references[id].Select(r => modules[r]).ToList();
}
return new ProjectGraph(modules.Values.ToList());
}
public Module Get(string name)
{
return Modules.Single(m => m.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
}
}
class Module : IEquatable<Module>, IComparable<Module>
{
public Guid Id { get; }
public string Name { get; }
private IReadOnlyList<Module> _dependencies;
public IReadOnlyList<Module> Dependencies
{
get => _dependencies;
[MemberNotNull(nameof(_dependencies))]
set
{
_dependencies = value;
_flattenedDependencies = null;
}
}
private IReadOnlyList<Module>? _flattenedDependencies;
public IReadOnlyList<Module> FlattenedDependencies
{
get => _flattenedDependencies ??= CalculateFlattenedDependencies();
set => _flattenedDependencies = value;
}
public Module(Guid id, string name)
{
Id = id;
Name = name;
Dependencies = [];
}
private IReadOnlyList<Module> CalculateFlattenedDependencies()
{
var queue = new Queue<Module>(_dependencies);
var flattenedDependencies = new HashSet<Module>();
while (queue.TryDequeue(out var dependency))
{
if (!flattenedDependencies.Add(dependency))
continue;
foreach (var childDependency in dependency.Dependencies)
{
queue.Enqueue(childDependency);
}
}
return flattenedDependencies.ToList();
}
public int CompareTo(Module? other)
{
if (ReferenceEquals(this, other))
return 0;
if (ReferenceEquals(null, other))
return 1;
if (FlattenedDependencies.Contains(other))
return 1;
if (other.FlattenedDependencies.Contains(this))
return 1;
return string.Compare(Name, other.Name, StringComparison.OrdinalIgnoreCase);
}
public bool Equals(Module? other)
{
if (ReferenceEquals(null, other))
return false;
if (ReferenceEquals(this, other))
return true;
return Id.Equals(other.Id);
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != this.GetType())
return false;
return Equals((Module)obj);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment