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

Introduction of solution info and project file info #295

Open
wants to merge 2 commits 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
3 changes: 1 addition & 2 deletions src/Buildalyzer.Workspaces/AnalyzerManagerExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using Microsoft.Build.Construction;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;

namespace Buildalyzer.Workspaces;

public static class AnalyzerManagerExtensions
Expand Down Expand Up @@ -39,7 +38,7 @@ public static AdhocWorkspace GetWorkspace(this IAnalyzerManager manager)
AdhocWorkspace workspace = manager.CreateWorkspace();
if (!string.IsNullOrEmpty(manager.SolutionFilePath))
{
SolutionInfo solutionInfo = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default, manager.SolutionFilePath);
Microsoft.CodeAnalysis.SolutionInfo solutionInfo = Microsoft.CodeAnalysis.SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default, manager.SolutionFilePath);
workspace.AddSolution(solutionInfo);

// Sort the projects so the order that they're added to the workspace in the same order as the solution file
Expand Down
6 changes: 3 additions & 3 deletions src/Buildalyzer.Workspaces/AnalyzerResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static Project AddToWorkspace(this IAnalyzerResult analyzerResult, Worksp
analyzerResult.Manager.WorkspaceProjectReferences[projectId.Id] = analyzerResult.ProjectReferences.ToArray();

// Create and add the project, but only if it's a support Roslyn project type
ProjectInfo projectInfo = GetProjectInfo(analyzerResult, workspace, projectId);
Microsoft.CodeAnalysis.ProjectInfo projectInfo = GetProjectInfo(analyzerResult, workspace, projectId);
if (projectInfo is null)
{
// Something went wrong (maybe not a support project type), so don't add this project
Expand Down Expand Up @@ -137,14 +137,14 @@ public static Project AddToWorkspace(this IAnalyzerResult analyzerResult, Worksp
return workspace.CurrentSolution.GetProject(projectId);
}

private static ProjectInfo GetProjectInfo(IAnalyzerResult analyzerResult, Workspace workspace, ProjectId projectId)
private static Microsoft.CodeAnalysis.ProjectInfo GetProjectInfo(IAnalyzerResult analyzerResult, Workspace workspace, ProjectId projectId)
{
string projectName = Path.GetFileNameWithoutExtension(analyzerResult.ProjectFilePath);
if (!TryGetSupportedLanguageName(analyzerResult.ProjectFilePath, out string languageName))
{
return null;
}
return ProjectInfo.Create(
return Microsoft.CodeAnalysis.ProjectInfo.Create(
projectId,
VersionStamp.Create(),
projectName,
Expand Down
67 changes: 35 additions & 32 deletions src/Buildalyzer/AnalyzerManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
extern alias StructuredLogger;
using System.Collections.Concurrent;
using System.IO;
using Buildalyzer.IO;
using Buildalyzer.Logging;
using Microsoft.Build.Construction;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -35,36 +36,44 @@
internal ConcurrentDictionary<Guid, string[]> WorkspaceProjectReferences = new ConcurrentDictionary<Guid, string[]>();
#pragma warning restore SA1401 // Fields should be private

public string SolutionFilePath { get; }
[Obsolete("Use SolutionInfo.Path instead.")]
public string? SolutionFilePath => SolutionInfo?.Path.ToString();

public SolutionFile SolutionFile { get; }
[Obsolete("Use SolutionInfo instead.")]
public SolutionFile? SolutionFile => SolutionInfo?.File;

public AnalyzerManager(AnalyzerManagerOptions options = null)
public SolutionInfo? SolutionInfo { get; }

public AnalyzerManager(AnalyzerManagerOptions? options = null)
: this(null, options)
{
}

public AnalyzerManager(string solutionFilePath, AnalyzerManagerOptions options = null)
public AnalyzerManager(string? solutionFilePath, AnalyzerManagerOptions? options = null)
{
options ??= new AnalyzerManagerOptions();
LoggerFactory = options.LoggerFactory;

if (!string.IsNullOrEmpty(solutionFilePath))
var path = IOPath.Parse(solutionFilePath);

if (path.HasValue && path.File().Exists)
{
SolutionFilePath = NormalizePath(solutionFilePath);
SolutionFile = SolutionFile.Parse(SolutionFilePath);
SolutionInfo = SolutionInfo.Load(path, Filter);

var lookup = SolutionInfo.File.ProjectsInOrder.ToDictionary(p => Guid.Parse(p.ProjectGuid), p => p);

// Initialize all the projects in the solution
foreach (ProjectInSolution projectInSolution in SolutionFile.ProjectsInOrder)
// init projects.
foreach (var proj in SolutionInfo)
{
if (!SupportedProjectTypes.Contains(projectInSolution.ProjectType)
|| (options?.ProjectFilter != null && !options.ProjectFilter(projectInSolution)))
{
continue;
}
GetProject(projectInSolution.AbsolutePath, projectInSolution);
var file = lookup[proj.Guid];
var analyzer = new ProjectAnalyzer(this, proj.Path.ToString(), file);
_projects.TryAdd(proj.Path.ToString(), analyzer);
}
}

bool Filter(ProjectInSolution p)
=> SupportedProjectTypes.Contains(p.ProjectType)
&& (options?.ProjectFilter?.Invoke(p) ?? true);
}

public void SetGlobalProperty(string key, string value)
Expand All @@ -83,10 +92,20 @@
EnvironmentVariables[key] = value;
}

public IProjectAnalyzer GetProject(string projectFilePath) => GetProject(projectFilePath, null);
public IProjectAnalyzer GetProject(string projectFilePath)
{
Guard.NotNull(projectFilePath);
projectFilePath = NormalizePath(projectFilePath);

if (!File.Exists(projectFilePath))
{
throw new FileNotFoundException("Could not load hte project file.", projectFilePath);
}
return _projects.GetOrAdd(projectFilePath, new ProjectAnalyzer(this, projectFilePath, null));
}

/// <inheritdoc/>
public IAnalyzerResults Analyze(string binLogPath, IEnumerable<Microsoft.Build.Framework.ILogger> buildLoggers = null)

Check warning on line 108 in src/Buildalyzer/AnalyzerManager.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 108 in src/Buildalyzer/AnalyzerManager.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 108 in src/Buildalyzer/AnalyzerManager.cs

View workflow job for this annotation

GitHub Actions / Build (macos-12)

Cannot convert null literal to non-nullable reference type.
{
binLogPath = NormalizePath(binLogPath);
if (!File.Exists(binLogPath))
Expand All @@ -104,22 +123,6 @@
};
}

private IProjectAnalyzer GetProject(string projectFilePath, ProjectInSolution projectInSolution)
{
Guard.NotNull(projectFilePath);

projectFilePath = NormalizePath(projectFilePath);
if (!File.Exists(projectFilePath))
{
if (projectInSolution == null)
{
throw new ArgumentException($"The path {projectFilePath} could not be found.");
}
return null;
}
return _projects.GetOrAdd(projectFilePath, new ProjectAnalyzer(this, projectFilePath, projectInSolution));
}

internal static string NormalizePath(string path) =>
path == null ? null : Path.GetFullPath(path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar));
}
2 changes: 2 additions & 0 deletions src/Buildalyzer/Buildalyzer.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
<![CDATA[
ToBeReleased
- Drop Buildalyzer.EmptyDisposable. (BREAKING)
- Introduction of SolutionInfo and ProjectInfo.
- Marked IAnalyzerManger.SolutionFile and IAnalyzer.SolutionFilePath as obsolete.
]]>
</PackageReleaseNotes>
</PropertyGroup>
Expand Down
9 changes: 7 additions & 2 deletions src/Buildalyzer/IAnalyzerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@

IReadOnlyDictionary<string, IProjectAnalyzer> Projects { get; }

SolutionFile SolutionFile { get; }
/// <inheritdoc cref="Buildalyzer.SolutionInfo" />
SolutionInfo? SolutionInfo { get; }

string SolutionFilePath { get; }
[Obsolete("Use SolutionInfo instead.")]
SolutionFile? SolutionFile { get; }

[Obsolete("Use SolutionInfo.Path instead.")]
string? SolutionFilePath { get; }

/// <summary>
/// Analyzes an MSBuild binary log file.
Expand All @@ -19,7 +24,7 @@
/// <param name="binLogPath">The path to the binary log file.</param>
/// <param name="buildLoggers">MSBuild loggers to replay events from the log to.</param>
/// <returns>A dictionary of target frameworks to <see cref="AnalyzerResult"/>.</returns>
IAnalyzerResults Analyze(string binLogPath, IEnumerable<Microsoft.Build.Framework.ILogger> buildLoggers = null);

Check warning on line 27 in src/Buildalyzer/IAnalyzerManager.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 27 in src/Buildalyzer/IAnalyzerManager.cs

View workflow job for this annotation

GitHub Actions / Build (macos-12)

Cannot convert null literal to non-nullable reference type.

IProjectAnalyzer GetProject(string projectFilePath);

Expand Down
3 changes: 3 additions & 0 deletions src/Buildalyzer/IO/IOPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ namespace Buildalyzer.IO;

private IOPath(string path) => _path = path;

/// <summary>Returns true if the path is not empty.</summary>
public bool HasValue => _path is { Length: > 0 };

/// <summary>Creates a <see cref="DirectoryInfo"/> based on the path.</summary>
[Pure]
public DirectoryInfo Directory() => new(ToString());
Expand Down
4 changes: 2 additions & 2 deletions src/Buildalyzer/ProjectAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

public string SolutionDirectory { get; }

public ProjectInSolution ProjectInSolution { get; }
public ProjectInSolution? ProjectInSolution { get; }

Check warning on line 34 in src/Buildalyzer/ProjectAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Nullability of reference types in return type of 'ProjectInSolution? ProjectAnalyzer.ProjectInSolution.get' doesn't match implicitly implemented member 'ProjectInSolution IProjectAnalyzer.ProjectInSolution.get' (possibly because of nullability attributes).

Check warning on line 34 in src/Buildalyzer/ProjectAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Nullability of reference types in return type of 'ProjectInSolution? ProjectAnalyzer.ProjectInSolution.get' doesn't match implicitly implemented member 'ProjectInSolution IProjectAnalyzer.ProjectInSolution.get' (possibly because of nullability attributes).

Check warning on line 34 in src/Buildalyzer/ProjectAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Build (macos-12)

Nullability of reference types in return type of 'ProjectInSolution? ProjectAnalyzer.ProjectInSolution.get' doesn't match implicitly implemented member 'ProjectInSolution IProjectAnalyzer.ProjectInSolution.get' (possibly because of nullability attributes).

/// <inheritdoc/>
public Guid ProjectGuid { get; }
Expand All @@ -50,7 +50,7 @@
public bool IgnoreFaultyImports { get; set; } = true;

// The project file path should already be normalized
internal ProjectAnalyzer(AnalyzerManager manager, string projectFilePath, ProjectInSolution projectInSolution)
internal ProjectAnalyzer(AnalyzerManager manager, string projectFilePath, ProjectInSolution? projectInSolution)
{
Manager = manager;
Logger = Manager.LoggerFactory?.CreateLogger<ProjectAnalyzer>();
Expand Down Expand Up @@ -400,7 +400,7 @@
}

public void AddBinaryLogger(
string binaryLogFilePath = null,

Check warning on line 403 in src/Buildalyzer/ProjectAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Build (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 403 in src/Buildalyzer/ProjectAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Build (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 403 in src/Buildalyzer/ProjectAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Build (macos-12)

Cannot convert null literal to non-nullable reference type.
BinaryLogger.ProjectImportsCollectionMode collectProjectImports = BinaryLogger.ProjectImportsCollectionMode.Embed) =>
AddBuildLogger(new BinaryLogger
{
Expand Down
40 changes: 40 additions & 0 deletions src/Buildalyzer/ProjectInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Buildalyzer.Construction;
using Buildalyzer.IO;
using Microsoft.Build.Construction;

namespace Buildalyzer;

/// <summary>Represents info about the MS Build solution file.</summary>
[DebuggerDisplay("{DebuggerDisplay}")]
public sealed class ProjectInfo
{
private ProjectInfo(IOPath path, Guid guid)
{
Path = path;
File = new ProjectFile(path.ToString());
Guid = guid;
}

/// <summary>The GUID of the project.</summary>
public Guid Guid { get; }

/// <summary>The path to the solution.</summary>
public IOPath Path { get; }

public IProjectFile File { get; }

[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"{Path.File().Name}, TFM = {string.Join(", ", File.TargetFrameworks)}";

/// <summary>Loads the <see cref="ProjectInfo"/> from disk.</summary>
/// <param name="path">
/// The path to load from.
/// </param>
[Pure]
public static ProjectInfo Load(IOPath path)
=> new(path, ProjectGuid.Create(path.File().Name));

[Pure]
internal static ProjectInfo New(ProjectInSolution proj)
=> new(IOPath.Parse(proj.AbsolutePath), Guid.Parse(proj.ProjectGuid));
}
64 changes: 64 additions & 0 deletions src/Buildalyzer/SolutionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#pragma warning disable CA1710 // Identifiers should have correct suffix: being a collection is not its main purpose.

using Buildalyzer.IO;
using Microsoft.Build.Construction;

namespace Buildalyzer;

/// <summary>Represents info about the MS Build solution file.</summary>
[DebuggerTypeProxy(typeof(Diagnostics.CollectionDebugView<ProjectInfo>))]
[DebuggerDisplay("{Path.File().Name}, Count = {Count}")]
public sealed class SolutionInfo : IReadOnlyCollection<ProjectInfo>
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly Dictionary<Guid, ProjectInfo> _lookup;

private SolutionInfo(IOPath path, SolutionFile file, Predicate<ProjectInSolution>? filter)
{
Path = path;
File = file;
Projects = file.ProjectsInOrder
.Where(p => (filter?.Invoke(p) ?? true) && System.IO.File.Exists(p.AbsolutePath))
.Select(ProjectInfo.New)
.ToImmutableArray();

_lookup = Projects.ToDictionary(p => p.Guid, p => p);
}

/// <summary>The path to the solution.</summary>
public IOPath Path { get; }

/// <summary>The <see cref="SolutionFile"/> representation of the solution.</summary>
internal SolutionFile File { get; }

/// <inheritdoc cref="SolutionFile.SolutionConfigurations" />
public IReadOnlyList<SolutionConfigurationInSolution> Configurations => File.SolutionConfigurations;

/// <summary>The projects in the solution.</summary>
public ImmutableArray<ProjectInfo> Projects { get; }

/// <summary>Tries to get a project based on its <see cref="ProjectInfo.Guid"/>.</summary>
public ProjectInfo? this[Guid projectGuid] => _lookup[projectGuid];

/// <inheritdoc />
public int Count => Projects.Length;

/// <inheritdoc />
[Pure]
public IEnumerator<ProjectInfo> GetEnumerator() => ((IReadOnlyCollection<ProjectInfo>)Projects).GetEnumerator();

/// <inheritdoc />
[Pure]
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

/// <summary>Loads the <see cref="SolutionInfo"/> from disk.</summary>
/// <param name="path">
/// The path to load from.
/// </param>
/// <param name="filter">
/// The project to include.
/// </param>
[Pure]
public static SolutionInfo Load(IOPath path, Predicate<ProjectInSolution>? filter = null)
=> new(path, SolutionFile.Parse(path.ToString()), filter);
}
Loading