|
@@ -7,7 +7,7 @@ using System.Threading.Tasks;
|
|
|
|
|
|
|
|
namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
{
|
|
{
|
|
|
- public class SolutionParser
|
|
|
|
|
|
|
+ public class SolutionParser
|
|
|
{
|
|
{
|
|
|
private static readonly Regex ProjectRegex = new Regex(
|
|
private static readonly Regex ProjectRegex = new Regex(
|
|
|
@"Project\(""{([^}]+)}""\)\s*=\s*""([^""]+)""\s*,\s*""([^""]+)""\s*,\s*""{([^}]+)}""",
|
|
@"Project\(""{([^}]+)}""\)\s*=\s*""([^""]+)""\s*,\s*""([^""]+)""\s*,\s*""{([^}]+)}""",
|
|
@@ -18,7 +18,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
|
|
|
|
|
|
|
private static readonly Regex ProjectReferenceRegex = new Regex(
|
|
private static readonly Regex ProjectReferenceRegex = new Regex(
|
|
|
- @"<ProjectReference\s+Include=""([^""]+)""\s*>.*?<Project>{([^}]+)}</Project>",
|
|
|
|
|
|
|
+ @"<ProjectReference\s+Include=""([^""]+)""\s*>.*?(?:<Project>{([^}]+)}</Project>)?",
|
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
|
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline);
|
|
|
|
|
|
|
|
// Known project type GUIDs
|
|
// Known project type GUIDs
|
|
@@ -27,31 +27,40 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
"{2150E333-8FDC-42A3-9474-1A3956D46DE8}" // Solution Folder
|
|
"{2150E333-8FDC-42A3-9474-1A3956D46DE8}" // Solution Folder
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ private Dictionary<string, List<string>> _allProjectDependencies = new Dictionary<string, List<string>>();
|
|
|
|
|
+
|
|
|
public async Task<List<ProjectNode>> ParseSolutionAsync(string solutionPath)
|
|
public async Task<List<ProjectNode>> ParseSolutionAsync(string solutionPath)
|
|
|
{
|
|
{
|
|
|
if (!File.Exists(solutionPath))
|
|
if (!File.Exists(solutionPath))
|
|
|
throw new FileNotFoundException($"Solution file not found: {solutionPath}");
|
|
throw new FileNotFoundException($"Solution file not found: {solutionPath}");
|
|
|
|
|
|
|
|
var projects = new List<ProjectNode>();
|
|
var projects = new List<ProjectNode>();
|
|
|
- var projectDependencies = new Dictionary<string, List<string>>();
|
|
|
|
|
|
|
+ _allProjectDependencies.Clear();
|
|
|
|
|
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
|
|
|
+ ErrorHandler.LogInfo($"Parsing solution: {Path.GetFileName(solutionPath)}");
|
|
|
|
|
+
|
|
|
var content = await FileUtils.ReadAllTextAsync(solutionPath);
|
|
var content = await FileUtils.ReadAllTextAsync(solutionPath);
|
|
|
|
|
|
|
|
// Parse projects first
|
|
// Parse projects first
|
|
|
ParseProjects(content, projects, solutionPath);
|
|
ParseProjects(content, projects, solutionPath);
|
|
|
|
|
+ ErrorHandler.LogInfo($"Found {projects.Count} projects in solution");
|
|
|
|
|
|
|
|
// Parse solution-level dependencies
|
|
// Parse solution-level dependencies
|
|
|
- ParseSolutionDependencies(content, projectDependencies);
|
|
|
|
|
|
|
+ ParseSolutionDependencies(content, _allProjectDependencies);
|
|
|
|
|
|
|
|
// Parse project file dependencies for more accurate dependency graph
|
|
// Parse project file dependencies for more accurate dependency graph
|
|
|
- await ParseProjectFileDependencies(projects, projectDependencies);
|
|
|
|
|
|
|
+ await ParseProjectFileDependencies(projects, _allProjectDependencies);
|
|
|
|
|
+
|
|
|
|
|
+ // Build the dependency tree
|
|
|
|
|
+ BuildDependencyTree(projects);
|
|
|
|
|
|
|
|
return projects;
|
|
return projects;
|
|
|
}
|
|
}
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
|
{
|
|
{
|
|
|
|
|
+ ErrorHandler.LogError($"Failed to parse solution file: {solutionPath}", ex);
|
|
|
throw new InvalidOperationException($"Failed to parse solution file: {ex.Message}", ex);
|
|
throw new InvalidOperationException($"Failed to parse solution file: {ex.Message}", ex);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -60,30 +69,41 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
{
|
|
{
|
|
|
var projectLookup = projects.ToDictionary(p => p.ProjectGuid, StringComparer.OrdinalIgnoreCase);
|
|
var projectLookup = projects.ToDictionary(p => p.ProjectGuid, StringComparer.OrdinalIgnoreCase);
|
|
|
|
|
|
|
|
|
|
+ // Clear existing dependencies
|
|
|
foreach (var project in projects)
|
|
foreach (var project in projects)
|
|
|
{
|
|
{
|
|
|
- // Build dependency relationships
|
|
|
|
|
- var projectInfo = project; // Assuming ProjectNode has ProjectInfo property
|
|
|
|
|
- if (projectInfo != null)
|
|
|
|
|
|
|
+ project.Dependencies.Clear();
|
|
|
|
|
+ project.Dependents.Clear();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Build dependency relationships from parsed data
|
|
|
|
|
+ foreach (var kvp in _allProjectDependencies)
|
|
|
|
|
+ {
|
|
|
|
|
+ var projectGuid = kvp.Key;
|
|
|
|
|
+ var dependencyGuids = kvp.Value;
|
|
|
|
|
+
|
|
|
|
|
+ if (projectLookup.TryGetValue(projectGuid, out var project))
|
|
|
{
|
|
{
|
|
|
- foreach (var depGuid in GetProjectDependencies(project.ProjectGuid))
|
|
|
|
|
|
|
+ foreach (var depGuid in dependencyGuids)
|
|
|
{
|
|
{
|
|
|
if (projectLookup.TryGetValue(depGuid, out var dependency))
|
|
if (projectLookup.TryGetValue(depGuid, out var dependency))
|
|
|
{
|
|
{
|
|
|
- project.Dependencies.Add(dependency);
|
|
|
|
|
- dependency.Dependents.Add(project);
|
|
|
|
|
|
|
+ if (!project.Dependencies.Contains(dependency))
|
|
|
|
|
+ {
|
|
|
|
|
+ project.Dependencies.Add(dependency);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!dependency.Dependents.Contains(project))
|
|
|
|
|
+ {
|
|
|
|
|
+ dependency.Dependents.Add(project);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
|
|
|
|
|
- private List<string> _projectDependencies = new List<string>();
|
|
|
|
|
-
|
|
|
|
|
- private List<string> GetProjectDependencies(string projectGuid)
|
|
|
|
|
- {
|
|
|
|
|
- // This would be populated during parsing
|
|
|
|
|
- return _projectDependencies;
|
|
|
|
|
|
|
+ // Log dependency information
|
|
|
|
|
+ var totalDependencies = _allProjectDependencies.Sum(kvp => kvp.Value.Count);
|
|
|
|
|
+ ErrorHandler.LogInfo($"Built dependency tree with {totalDependencies} total dependencies");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
public List<ProjectNode> GetProjectsWithDependencies(List<ProjectNode> selectedProjects)
|
|
public List<ProjectNode> GetProjectsWithDependencies(List<ProjectNode> selectedProjects)
|
|
@@ -96,7 +116,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
var current = toProcess.Dequeue();
|
|
var current = toProcess.Dequeue();
|
|
|
if (result.Add(current))
|
|
if (result.Add(current))
|
|
|
{
|
|
{
|
|
|
- // Add dependencies
|
|
|
|
|
|
|
+ // Add all dependencies recursively
|
|
|
foreach (var dependency in current.Dependencies)
|
|
foreach (var dependency in current.Dependencies)
|
|
|
{
|
|
{
|
|
|
if (!result.Contains(dependency))
|
|
if (!result.Contains(dependency))
|
|
@@ -107,6 +127,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ ErrorHandler.LogInfo($"Expanded {selectedProjects.Count} selected projects to {result.Count} projects with dependencies");
|
|
|
return result.ToList();
|
|
return result.ToList();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -118,6 +139,8 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
|
|
|
|
|
var filteredSolutionPath = Path.Combine(tempDir, $"{solutionName}_filtered.sln");
|
|
var filteredSolutionPath = Path.Combine(tempDir, $"{solutionName}_filtered.sln");
|
|
|
|
|
|
|
|
|
|
+ ErrorHandler.LogInfo($"Creating filtered solution: {Path.GetFileName(filteredSolutionPath)} with {selectedProjects.Count} projects");
|
|
|
|
|
+
|
|
|
// Convert ProjectNode to ProjectInfo for generator
|
|
// Convert ProjectNode to ProjectInfo for generator
|
|
|
var projectInfos = selectedProjects.Select(pn => new ProjectInfo
|
|
var projectInfos = selectedProjects.Select(pn => new ProjectInfo
|
|
|
{
|
|
{
|
|
@@ -130,6 +153,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
var generator = new FilteredSolutionGenerator();
|
|
var generator = new FilteredSolutionGenerator();
|
|
|
await generator.GenerateFilteredSolutionAsync(originalSolutionPath, filteredSolutionPath, projectInfos);
|
|
await generator.GenerateFilteredSolutionAsync(originalSolutionPath, filteredSolutionPath, projectInfos);
|
|
|
|
|
|
|
|
|
|
+ ErrorHandler.LogInfo($"Successfully created filtered solution: {filteredSolutionPath}");
|
|
|
return filteredSolutionPath;
|
|
return filteredSolutionPath;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -138,6 +162,8 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
var projectMatches = ProjectRegex.Matches(content);
|
|
var projectMatches = ProjectRegex.Matches(content);
|
|
|
var solutionDir = Path.GetDirectoryName(solutionPath);
|
|
var solutionDir = Path.GetDirectoryName(solutionPath);
|
|
|
|
|
|
|
|
|
|
+ ErrorHandler.LogInfo($"Parsing {projectMatches.Count} project entries from solution file");
|
|
|
|
|
+
|
|
|
foreach (Match match in projectMatches)
|
|
foreach (Match match in projectMatches)
|
|
|
{
|
|
{
|
|
|
var typeGuid = match.Groups[1].Value;
|
|
var typeGuid = match.Groups[1].Value;
|
|
@@ -147,7 +173,10 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
|
|
|
|
|
// Skip solution folders and other non-project items
|
|
// Skip solution folders and other non-project items
|
|
|
if (SolutionFolderTypes.Contains(typeGuid))
|
|
if (SolutionFolderTypes.Contains(typeGuid))
|
|
|
|
|
+ {
|
|
|
|
|
+ ErrorHandler.LogInfo($"Skipping solution folder: {name}");
|
|
|
continue;
|
|
continue;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// Resolve full path
|
|
// Resolve full path
|
|
|
var fullPath = Path.IsPathRooted(relativePath)
|
|
var fullPath = Path.IsPathRooted(relativePath)
|
|
@@ -157,7 +186,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
// Verify project file exists
|
|
// Verify project file exists
|
|
|
if (!File.Exists(fullPath))
|
|
if (!File.Exists(fullPath))
|
|
|
{
|
|
{
|
|
|
- System.Diagnostics.Debug.WriteLine($"Warning: Project file not found: {fullPath}");
|
|
|
|
|
|
|
+ ErrorHandler.LogError($"Warning: Project file not found: {fullPath}", null);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -176,6 +205,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
var lines = content.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
string currentProjectGuid = null;
|
|
string currentProjectGuid = null;
|
|
|
bool inProjectDependencies = false;
|
|
bool inProjectDependencies = false;
|
|
|
|
|
+ int dependencyCount = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < lines.Length; i++)
|
|
for (int i = 0; i < lines.Length; i++)
|
|
|
{
|
|
{
|
|
@@ -213,14 +243,20 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
{
|
|
{
|
|
|
var dependentGuid = depMatch.Groups[2].Value;
|
|
var dependentGuid = depMatch.Groups[2].Value;
|
|
|
AddDependency(projectDependencies, currentProjectGuid, dependentGuid);
|
|
AddDependency(projectDependencies, currentProjectGuid, dependentGuid);
|
|
|
|
|
+ dependencyCount++;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ ErrorHandler.LogInfo($"Parsed {dependencyCount} solution-level dependencies");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private async Task ParseProjectFileDependencies(List<ProjectNode> projects, Dictionary<string, List<string>> projectDependencies)
|
|
private async Task ParseProjectFileDependencies(List<ProjectNode> projects, Dictionary<string, List<string>> projectDependencies)
|
|
|
{
|
|
{
|
|
|
- foreach (var project in projects.ToList())
|
|
|
|
|
|
|
+ int projectRefCount = 0;
|
|
|
|
|
+ int processedProjects = 0;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var project in projects)
|
|
|
{
|
|
{
|
|
|
try
|
|
try
|
|
|
{
|
|
{
|
|
@@ -231,26 +267,54 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
|
|
|
|
|
|
|
|
foreach (Match match in referenceMatches)
|
|
foreach (Match match in referenceMatches)
|
|
|
{
|
|
{
|
|
|
- var referencedProjectGuid = match.Groups[2].Value;
|
|
|
|
|
- AddDependency(projectDependencies, project.ProjectGuid, referencedProjectGuid);
|
|
|
|
|
|
|
+ var referencedProjectPath = match.Groups[1].Value;
|
|
|
|
|
+ var referencedProjectGuid = match.Groups[2].Success ? match.Groups[2].Value : null;
|
|
|
|
|
+
|
|
|
|
|
+ // If no GUID in reference, try to find it by path
|
|
|
|
|
+ if (string.IsNullOrEmpty(referencedProjectGuid))
|
|
|
|
|
+ {
|
|
|
|
|
+ var fullRefPath = Path.IsPathRooted(referencedProjectPath)
|
|
|
|
|
+ ? referencedProjectPath
|
|
|
|
|
+ : Path.GetFullPath(Path.Combine(Path.GetDirectoryName(project.Path), referencedProjectPath));
|
|
|
|
|
+
|
|
|
|
|
+ var referencedProject = projects.FirstOrDefault(p =>
|
|
|
|
|
+ string.Equals(p.Path, fullRefPath, StringComparison.OrdinalIgnoreCase));
|
|
|
|
|
+
|
|
|
|
|
+ if (referencedProject != null)
|
|
|
|
|
+ {
|
|
|
|
|
+ referencedProjectGuid = referencedProject.ProjectGuid;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!string.IsNullOrEmpty(referencedProjectGuid))
|
|
|
|
|
+ {
|
|
|
|
|
+ AddDependency(projectDependencies, project.ProjectGuid, referencedProjectGuid);
|
|
|
|
|
+ projectRefCount++;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ processedProjects++;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
catch (Exception ex)
|
|
catch (Exception ex)
|
|
|
{
|
|
{
|
|
|
- System.Diagnostics.Debug.WriteLine($"Warning: Could not parse project file {project.Path}: {ex.Message}");
|
|
|
|
|
|
|
+ ErrorHandler.LogError($"Warning: Could not parse project file {project.Path}", ex);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ ErrorHandler.LogInfo($"Parsed {projectRefCount} project reference dependencies from {processedProjects} project files");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private void AddDependency(Dictionary<string, List<string>> projectDependencies, string projectGuid, string dependentGuid)
|
|
private void AddDependency(Dictionary<string, List<string>> projectDependencies, string projectGuid, string dependentGuid)
|
|
|
{
|
|
{
|
|
|
|
|
+ if (string.IsNullOrEmpty(projectGuid) || string.IsNullOrEmpty(dependentGuid))
|
|
|
|
|
+ return;
|
|
|
|
|
+
|
|
|
if (!projectDependencies.ContainsKey(projectGuid))
|
|
if (!projectDependencies.ContainsKey(projectGuid))
|
|
|
{
|
|
{
|
|
|
projectDependencies[projectGuid] = new List<string>();
|
|
projectDependencies[projectGuid] = new List<string>();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (!projectDependencies[projectGuid].Contains(dependentGuid))
|
|
|
|
|
|
|
+ if (!projectDependencies[projectGuid].Contains(dependentGuid, StringComparer.OrdinalIgnoreCase))
|
|
|
{
|
|
{
|
|
|
projectDependencies[projectGuid].Add(dependentGuid);
|
|
projectDependencies[projectGuid].Add(dependentGuid);
|
|
|
}
|
|
}
|