Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add caching #15

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
72 changes: 62 additions & 10 deletions src/Dependify.Core/MsBuildService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
using Microsoft.Build.Construction;
using Microsoft.Extensions.Logging;

public class MsBuildService : IDisposable

Check warning on line 9 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Provide an overridable implementation of Dispose(bool) on 'MsBuildService' or mark the type as sealed. A call to Dispose(false) should only clean up native resources. A call to Dispose(true) should clean up both managed and native resources. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063)

Check warning on line 9 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Provide an overridable implementation of Dispose(bool) on 'MsBuildService' or mark the type as sealed. A call to Dispose(false) should only clean up native resources. A call to Dispose(true) should clean up both managed and native resources. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063)

Check warning on line 9 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Provide an overridable implementation of Dispose(bool) on 'MsBuildService' or mark the type as sealed. A call to Dispose(false) should only clean up native resources. A call to Dispose(true) should clean up both managed and native resources. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063)

Check warning on line 9 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Provide an overridable implementation of Dispose(bool) on 'MsBuildService' or mark the type as sealed. A call to Dispose(false) should only clean up native resources. A call to Dispose(true) should clean up both managed and native resources. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063)
{
private readonly ILogger<MsBuildService> logger;
private readonly ILoggerFactory loggerFactory;
private readonly Subject<NodeEvent> subject;

Check warning on line 13 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

'MsBuildService' contains field 'subject' that is of IDisposable type 'Subject<NodeEvent>', but it is never disposed. Change the Dispose method on 'MsBuildService' to call Close or Dispose on this field. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2213)

public IObservable<NodeEvent> OnLoadingEvents { get; }

Expand All @@ -23,6 +23,24 @@
}

public DependencyGraph AnalyzeReferences(SolutionReferenceNode solution, MsBuildConfig config)
{
return this.AnalyzeReferencesCore(solution, config, null);
}

public DependencyGraph AnalyzeReferencesWithCache(
SolutionReferenceNode solution,
MsBuildConfig config,
DependencyGraph cache
)
{
return this.AnalyzeReferencesCore(solution, config, cache);
}

private DependencyGraph AnalyzeReferencesCore(
SolutionReferenceNode solution,
MsBuildConfig config,
DependencyGraph? cache
)
{
this.logger.LogInformation("Analyzing solution {Solution}", solution.Path);
this.subject.OnNext(new NodeEvent(NodeEventType.SolutionLoading, solution.Id, solution.Path));
Expand All @@ -40,10 +58,18 @@

foreach (var project in projects)
{
var projectNode = new ProjectReferenceNode(project.Key);
builder.WithEdge(new Edge(builder.Root, projectNode));
var projectNode = new ProjectReferenceNode(project.Value.ProjectFile.Path);

this.AddDependenciesToGraph(builder, project.Value, projectNode, config);
if (cache is not null && cache.Nodes.Contains(projectNode))
{
this.CopyFromCache(builder, cache, projectNode);
}
else
{
builder.WithEdge(new Edge(builder.Root, projectNode));

this.AddDependenciesToGraph(builder, project.Value, projectNode, config);
}
}

if (config.FullScan)
Expand All @@ -53,7 +79,7 @@
{
nodesToScan = builder.GetNotScannedNodes().OfType<ProjectReferenceNode>().ToList();

this.AnalyzeReferencesCore(builder, nodesToScan, config);
this.AnalyzeReferencesCore(builder, nodesToScan, config, cache);
} while (nodesToScan.Count > 0);
}

Expand All @@ -67,7 +93,7 @@
{
var builder = new DependencyGraph.Builder(node);

this.AnalyzeReferencesCore(builder, [node], config);
this.AnalyzeReferencesCore(builder, [node], config, null);

return builder.Build();
}
Expand All @@ -76,30 +102,39 @@
{
var builder = new DependencyGraph.Builder(new SolutionReferenceNode());

this.AnalyzeReferencesCore(builder, nodes, config);
this.AnalyzeReferencesCore(builder, nodes, config, null);

return builder.Build();
}

private void AnalyzeReferencesCore(
DependencyGraph.Builder builder,
IEnumerable<ProjectReferenceNode> nodes,
MsBuildConfig config
MsBuildConfig config,
DependencyGraph? cache
)
{
if (!nodes.Any())

Check warning on line 117 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1851)
{
return;
}

var analyzerManager = new AnalyzerManager(new AnalyzerManagerOptions { LoggerFactory = this.loggerFactory, });

foreach (var path in nodes.Select(n => n.Path))

Check warning on line 124 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1851)

Check warning on line 124 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1851)
{
var projectNode = new ProjectReferenceNode(path);
var project = analyzerManager.GetProject(path);

this.AddDependenciesToGraph(builder, project, projectNode, config);
if (cache is not null && cache.Nodes.Contains(projectNode))
{
this.CopyFromCache(builder, cache, projectNode);
}
else
{
var project = analyzerManager.GetProject(path);

this.AddDependenciesToGraph(builder, project, projectNode, config);
}
}

if (config.FullScan)
Expand All @@ -109,11 +144,28 @@
{
nodesToScan = builder.GetNotScannedNodes().OfType<ProjectReferenceNode>().ToList();

this.AnalyzeReferencesCore(builder, nodesToScan, config);
this.AnalyzeReferencesCore(builder, nodesToScan, config, cache);
} while (nodesToScan.Count > 0);
}
}

private void CopyFromCache(DependencyGraph.Builder builder, DependencyGraph cache, ProjectReferenceNode projectNode)
{
var subgraph = cache.SubGraph(projectNode);

this.logger.LogInformation("Loaded project {Project} from cache", projectNode.Path);

foreach (var node in subgraph.Nodes)
{
builder.WithNode(node, scanned: true);
}

foreach (var edge in subgraph.Edges)
{
builder.WithEdge(edge);
}
}

private void AddDependenciesToGraph(
DependencyGraph.Builder builder,
IProjectAnalyzer projectAnalyzer,
Expand Down Expand Up @@ -161,7 +213,7 @@
}
}

public void Dispose()

Check warning on line 216 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Modify 'MsBuildService.Dispose' so that it calls Dispose(true), then calls GC.SuppressFinalize on the current object instance ('this' or 'Me' in Visual Basic), and then returns (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063)

Check warning on line 216 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Modify 'MsBuildService.Dispose' so that it calls Dispose(true), then calls GC.SuppressFinalize on the current object instance ('this' or 'Me' in Visual Basic), and then returns (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1063)

Check warning on line 216 in src/Dependify.Core/MsBuildService.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Change MsBuildService.Dispose() to call GC.SuppressFinalize(object). This will prevent derived types that introduce a finalizer from needing to re-implement 'IDisposable' to call it. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1816)
{
this.subject.OnCompleted();
}
Expand Down
10 changes: 8 additions & 2 deletions src/Dependify.Core/SolutionRegistry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Reactive.Subjects;
using Dependify.Core.Graph;

public class SolutionRegistry

Check warning on line 7 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Type 'SolutionRegistry' owns disposable field(s) 'subject' but is not disposable (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1001)
{
private readonly Dictionary<SolutionReferenceNode, DependencyGraph> solutionGraphs = [];
private static readonly object LockObject = new();
Expand All @@ -20,7 +20,7 @@
public IList<Node> Nodes { get; private set; }
public bool IsLoaded { get; private set; }

public SolutionRegistry(FileProviderProjectLocator projectLocator, MsBuildService buildService)

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Non-nullable property 'OnProgress' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Non-nullable property 'Nodes' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Non-nullable property 'OnProgress' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-ubuntu-latest

Non-nullable property 'Nodes' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Non-nullable property 'OnProgress' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Non-nullable property 'Nodes' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Non-nullable property 'OnProgress' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 23 in src/Dependify.Core/SolutionRegistry.cs

View workflow job for this annotation

GitHub Actions / Build-windows-latest

Non-nullable property 'Nodes' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
{
this.projectLocator = projectLocator;
this.buildService = buildService;
Expand All @@ -46,7 +46,7 @@

public Task LoadSolutionsAsync(MsBuildConfig msBuildConfig, CancellationToken cancellationToken = default)
{
this.IsLoaded = false;
this.ResetCache();

lock (LockObject)
{
Expand All @@ -61,7 +61,7 @@
this.Nodes.OfType<ProjectReferenceNode>().ToList(),
msBuildConfig
)
: this.buildService.AnalyzeReferences(solution, msBuildConfig);
: this.buildService.AnalyzeReferencesWithCache(solution, msBuildConfig, this.GetFullGraph());

this.solutionGraphs[solution] = dependencyGraph;

Expand Down Expand Up @@ -131,6 +131,12 @@

return builder.Build();
}

private void ResetCache()
{
this.IsLoaded = false;
this.solutionGraphs.Clear();
}
}

public record NodeUsage(
Expand Down
Loading