Skip to content

Commit c59f2a1

Browse files
Copilotericstj
andcommitted
Implement project properties plumbing and improved deduplication logic
Co-authored-by: ericstj <[email protected]>
1 parent 61d0f7b commit c59f2a1

File tree

5 files changed

+177
-144
lines changed

5 files changed

+177
-144
lines changed

src/SourceBrowser/src/BinLogParser/BinLogReader.cs

Lines changed: 70 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ void TryGetInvocationFromEvent(object sender, BuildEventArgs args)
5252
}
5353
}
5454

55-
reader.TargetStarted += TryGetInvocationFromEvent;
55+
reader.TargetStarted += TryGetInvocationFromEvent;
5656
reader.MessageRaised += TryGetInvocationFromEvent;
5757

5858
reader.Replay(binLogFilePath);
@@ -81,40 +81,40 @@ private static List<CompilerInvocation> ExtractInvocationsFromBuild(string logFi
8181
return invocations;
8282
}
8383

84-
private static CompilerInvocation TryGetInvocationFromRecord(BuildEventArgs args, Dictionary<(int, int), CompilerInvocation> taskIdToInvocationMap)
85-
{
86-
int targetId = args.BuildEventContext?.TargetId ?? -1;
87-
int projectId = args.BuildEventContext?.ProjectInstanceId ?? -1;
88-
if (targetId < 0)
89-
{
90-
return null;
91-
}
92-
93-
var targetStarted = args as TargetStartedEventArgs;
94-
if (targetStarted != null && targetStarted.TargetName == "CoreCompile")
95-
{
96-
var invocation = new CompilerInvocation();
97-
taskIdToInvocationMap[(targetId, projectId)] = invocation;
98-
invocation.ProjectFilePath = targetStarted.ProjectFile;
99-
return null;
100-
}
101-
102-
var commandLine = GetCommandLineFromEventArgs(args, out var language);
103-
if (commandLine == null)
104-
{
105-
return null;
106-
}
107-
108-
CompilerInvocation compilerInvocation;
109-
if (taskIdToInvocationMap.TryGetValue((targetId, projectId), out compilerInvocation))
110-
{
111-
compilerInvocation.Language = language == CompilerKind.CSharp ? LanguageNames.CSharp : LanguageNames.VisualBasic;
112-
compilerInvocation.CommandLineArguments = commandLine;
113-
Populate(compilerInvocation);
114-
taskIdToInvocationMap.Remove((targetId, projectId));
115-
}
116-
117-
return compilerInvocation;
84+
private static CompilerInvocation TryGetInvocationFromRecord(BuildEventArgs args, Dictionary<(int, int), CompilerInvocation> taskIdToInvocationMap)
85+
{
86+
int targetId = args.BuildEventContext?.TargetId ?? -1;
87+
int projectId = args.BuildEventContext?.ProjectInstanceId ?? -1;
88+
if (targetId < 0)
89+
{
90+
return null;
91+
}
92+
93+
var targetStarted = args as TargetStartedEventArgs;
94+
if (targetStarted != null && targetStarted.TargetName == "CoreCompile")
95+
{
96+
var invocation = new CompilerInvocation();
97+
taskIdToInvocationMap[(targetId, projectId)] = invocation;
98+
invocation.ProjectFilePath = targetStarted.ProjectFile;
99+
return null;
100+
}
101+
102+
var commandLine = GetCommandLineFromEventArgs(args, out var language);
103+
if (commandLine == null)
104+
{
105+
return null;
106+
}
107+
108+
CompilerInvocation compilerInvocation;
109+
if (taskIdToInvocationMap.TryGetValue((targetId, projectId), out compilerInvocation))
110+
{
111+
compilerInvocation.Language = language == CompilerKind.CSharp ? LanguageNames.CSharp : LanguageNames.VisualBasic;
112+
compilerInvocation.CommandLineArguments = commandLine;
113+
Populate(compilerInvocation);
114+
taskIdToInvocationMap.Remove((targetId, projectId));
115+
}
116+
117+
return compilerInvocation;
118118
}
119119

120120
private static void Populate(CompilerInvocation compilerInvocation)
@@ -125,25 +125,41 @@ private static void Populate(CompilerInvocation compilerInvocation)
125125
}
126126
}
127127

128-
private static CompilerInvocation TryGetInvocationFromTask(Microsoft.Build.Logging.StructuredLogger.Task task)
129-
{
130-
var name = task.Name;
131-
if (name != "Csc" && name != "Vbc" || ((task.Parent as Microsoft.Build.Logging.StructuredLogger.Target)?.Name != "CoreCompile"))
132-
{
133-
return null;
134-
}
135-
136-
var language = name == "Csc" ? LanguageNames.CSharp : LanguageNames.VisualBasic;
137-
var commandLine = task.CommandLineArguments;
138-
commandLine = TrimCompilerExeFromCommandLine(commandLine, name == "Csc"
139-
? CompilerKind.CSharp
140-
: CompilerKind.VisualBasic);
141-
return new CompilerInvocation
142-
{
143-
Language = language,
144-
CommandLineArguments = commandLine,
145-
ProjectFilePath = task.GetNearestParent<Microsoft.Build.Logging.StructuredLogger.Project>()?.ProjectFile
146-
};
128+
private static CompilerInvocation TryGetInvocationFromTask(Microsoft.Build.Logging.StructuredLogger.Task task)
129+
{
130+
var name = task.Name;
131+
if (name != "Csc" && name != "Vbc" || ((task.Parent as Microsoft.Build.Logging.StructuredLogger.Target)?.Name != "CoreCompile"))
132+
{
133+
return null;
134+
}
135+
136+
var language = name == "Csc" ? LanguageNames.CSharp : LanguageNames.VisualBasic;
137+
var commandLine = task.CommandLineArguments;
138+
commandLine = TrimCompilerExeFromCommandLine(commandLine, name == "Csc"
139+
? CompilerKind.CSharp
140+
: CompilerKind.VisualBasic);
141+
142+
var invocation = new CompilerInvocation
143+
{
144+
Language = language,
145+
CommandLineArguments = commandLine,
146+
ProjectFilePath = task.GetNearestParent<Microsoft.Build.Logging.StructuredLogger.Project>()?.ProjectFile
147+
};
148+
149+
// Capture project properties from the project
150+
var project = task.GetNearestParent<Microsoft.Build.Logging.StructuredLogger.Project>();
151+
if (project != null)
152+
{
153+
project.VisitAllChildren<Microsoft.Build.Logging.StructuredLogger.Property>(prop =>
154+
{
155+
if (!string.IsNullOrEmpty(prop.Name) && !string.IsNullOrEmpty(prop.Value))
156+
{
157+
invocation.ProjectProperties[prop.Name] = prop.Value;
158+
}
159+
});
160+
}
161+
162+
return invocation;
147163
}
148164

149165
public static string TrimCompilerExeFromCommandLine(string commandLine, CompilerKind language)

src/SourceBrowser/src/BinLogParser/CompilerInvocation.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@
99

1010
namespace Microsoft.SourceBrowser.BinLogParser
1111
{
12-
public class CompilerInvocation
13-
{
14-
public string ProjectFilePath { get; set; }
15-
public string ProjectDirectory => ProjectFilePath == null ? "" : Path.GetDirectoryName(ProjectFilePath);
16-
public string OutputAssemblyPath { get; set; }
17-
public string CommandLineArguments { get; set; }
18-
public string SolutionRoot { get; set; }
19-
public IEnumerable<string> TypeScriptFiles { get; set; }
12+
public class CompilerInvocation
13+
{
14+
public string ProjectFilePath { get; set; }
15+
public string ProjectDirectory => ProjectFilePath == null ? "" : Path.GetDirectoryName(ProjectFilePath);
16+
public string OutputAssemblyPath { get; set; }
17+
public string CommandLineArguments { get; set; }
18+
public string SolutionRoot { get; set; }
19+
public IEnumerable<string> TypeScriptFiles { get; set; }
20+
public Dictionary<string, string> ProjectProperties { get; set; } = new Dictionary<string, string>();
2021

2122
public string AssemblyName => Path.GetFileNameWithoutExtension(OutputAssemblyPath);
2223

src/SourceBrowser/src/BinLogToSln/BinLogToSln.csproj

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
<VersionPrefix>1.0.1</VersionPrefix>
1010
</PropertyGroup>
1111

12-
<ItemGroup>
13-
<PackageReference Include="LibGit2Sharp" />
14-
<PackageReference Include="Mono.Options" />
15-
<PackageReference Include="System.Reflection.Metadata" />
12+
<ItemGroup>
13+
<PackageReference Include="LibGit2Sharp" />
14+
<PackageReference Include="Mono.Options" />
15+
<PackageReference Include="NuGet.Frameworks" />
16+
<PackageReference Include="System.Reflection.Metadata" />
1617
</ItemGroup>
1718

1819
<ItemGroup>

src/SourceBrowser/src/BinLogToSln/Program.cs

Lines changed: 85 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Diagnostics.CodeAnalysis;
4-
using System.IO;
5-
using System.Linq;
6-
using System.Numerics;
7-
using System.Reflection.Metadata;
8-
using System.Reflection.PortableExecutable;
9-
using LibGit2Sharp;
10-
using Microsoft.CodeAnalysis;
11-
using Microsoft.CodeAnalysis.CSharp;
12-
using Microsoft.SourceBrowser.BinLogParser;
13-
using Mono.Options;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics.CodeAnalysis;
4+
using System.IO;
5+
using System.Linq;
6+
using System.Numerics;
7+
using System.Reflection.Metadata;
8+
using System.Reflection.PortableExecutable;
9+
using LibGit2Sharp;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.SourceBrowser.BinLogParser;
13+
using Mono.Options;
14+
using NuGet.Frameworks;
1415

15-
namespace BinLogToSln
16-
{
16+
namespace BinLogToSln
17+
{
1718
class Program
1819
{
1920
private static CompilerInvocation SelectBestInvocation(IGrouping<string, CompilerInvocation> invocationGroup)
@@ -46,37 +47,67 @@ private static int CalculateInvocationScore(CompilerInvocation invocation)
4647

4748
try
4849
{
49-
// Prefer invocations with non-*.notsupported.cs source files
50+
// Check for UseForSourceIndex escape hatch
51+
if (invocation.ProjectProperties.TryGetValue("UseForSourceIndex", out var useForSourceIndex) &&
52+
bool.TryParse(useForSourceIndex, out var shouldUse) && shouldUse)
53+
{
54+
return int.MaxValue; // Highest possible score
55+
}
56+
57+
// Check for IsPlatformNotSupportedAssembly property
58+
if (invocation.ProjectProperties.TryGetValue("IsPlatformNotSupportedAssembly", out var isPlatformNotSupported) &&
59+
bool.TryParse(isPlatformNotSupported, out var isNotSupported) && isNotSupported)
60+
{
61+
score -= 10000; // Heavy penalty for platform not supported assemblies
62+
}
63+
64+
// Prefer invocations with actual source files
5065
var sourceFiles = invocation.Parsed?.SourceFiles;
5166
if (sourceFiles.HasValue)
5267
{
5368
int totalSourceFiles = sourceFiles.Value.Length;
54-
int notSupportedFiles = sourceFiles.Value.Count(sf => sf.Path.Contains(".notsupported.cs"));
69+
score += totalSourceFiles * 100; // Base score for having source files
5570

56-
if (totalSourceFiles > 0)
71+
// Bonus for having more source files (indicates more complete implementation)
72+
if (totalSourceFiles > 10)
5773
{
58-
// Higher score for projects with fewer notsupported files relative to total
59-
score += (totalSourceFiles - notSupportedFiles) * 100;
74+
score += 500;
75+
}
76+
}
77+
78+
// Prefer more specific target frameworks using NuGet.Frameworks
79+
if (invocation.ProjectProperties.TryGetValue("TargetFramework", out var targetFramework) &&
80+
!string.IsNullOrEmpty(targetFramework))
81+
{
82+
try
83+
{
84+
var framework = NuGetFramework.Parse(targetFramework);
6085

61-
// Bonus for having no notsupported files at all
62-
if (notSupportedFiles == 0)
86+
// Prefer platform-specific frameworks
87+
if (framework.HasPlatform)
6388
{
6489
score += 1000;
6590
}
66-
}
67-
}
91+
92+
// Additional scoring based on framework specificity
93+
if (framework.Platform != null)
94+
{
95+
var platformString = framework.Platform.ToLowerInvariant();
96+
if (platformString.Contains("linux") || platformString.Contains("windows") || platformString.Contains("osx"))
97+
{
98+
score += 500;
99+
}
100+
}
68101

69-
// Prefer more specific target frameworks
70-
// This is a heuristic - longer framework names are typically more specific
71-
string targetFramework = GetTargetFrameworkFromCommandLine(invocation.CommandLineArguments);
72-
if (!string.IsNullOrEmpty(targetFramework))
73-
{
74-
score += targetFramework.Length * 10;
75-
76-
// Prefer platform-specific frameworks over generic ones
77-
if (targetFramework.Contains("linux") || targetFramework.Contains("windows") || targetFramework.Contains("osx"))
102+
// Prefer newer frameworks
103+
if (framework.Version != null)
104+
{
105+
score += (int)(framework.Version.Major * 10 + framework.Version.Minor);
106+
}
107+
}
108+
catch (Exception ex)
78109
{
79-
score += 500;
110+
Console.WriteLine($"Warning: Could not parse TargetFramework '{targetFramework}': {ex.Message}");
80111
}
81112
}
82113
}
@@ -90,34 +121,27 @@ private static int CalculateInvocationScore(CompilerInvocation invocation)
90121
return score;
91122
}
92123

93-
private static string GetTargetFrameworkFromCommandLine(string commandLineArguments)
124+
private static bool ShouldExcludeInvocation(CompilerInvocation invocation)
94125
{
95-
if (string.IsNullOrEmpty(commandLineArguments))
96-
return null;
97-
98-
// Look for /p:TargetFramework=xxx pattern
99-
var match = System.Text.RegularExpressions.Regex.Match(
100-
commandLineArguments,
101-
@"/p:TargetFramework=([^\s;]+)",
102-
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
103-
104-
if (match.Success)
126+
if (string.IsNullOrEmpty(invocation.ProjectDirectory))
105127
{
106-
return match.Groups[1].Value;
128+
return true;
107129
}
108130

109-
// Look for --target-framework xxx pattern
110-
match = System.Text.RegularExpressions.Regex.Match(
111-
commandLineArguments,
112-
@"--target-framework\s+([^\s]+)",
113-
System.Text.RegularExpressions.RegexOptions.IgnoreCase);
131+
string projectFolder = Path.GetFileName(invocation.ProjectDirectory);
132+
if (projectFolder == "ref" || projectFolder == "stubs")
133+
{
134+
Console.WriteLine($"Skipping Ref Assembly project {invocation.ProjectFilePath}");
135+
return true;
136+
}
114137

115-
if (match.Success)
138+
if (Path.GetFileName(Path.GetDirectoryName(invocation.ProjectDirectory)) == "cycle-breakers")
116139
{
117-
return match.Groups[1].Value;
140+
Console.WriteLine($"Skipping Wpf Cycle-Breaker project {invocation.ProjectFilePath}");
141+
return true;
118142
}
119-
120-
return null;
143+
144+
return false;
121145
}
122146
static void Main(string[] args)
123147
{
@@ -181,22 +205,7 @@ static void Main(string[] args)
181205

182206
// Group invocations by assembly name and select the best one for each
183207
var invocationGroups = invocations
184-
.Where(invocation => !string.IsNullOrEmpty(invocation.ProjectDirectory))
185-
.Where(invocation =>
186-
{
187-
string projectFolder = Path.GetFileName(invocation.ProjectDirectory);
188-
if (projectFolder == "ref" || projectFolder == "stubs")
189-
{
190-
Console.WriteLine($"Skipping Ref Assembly project {invocation.ProjectFilePath}");
191-
return false;
192-
}
193-
if (Path.GetFileName(Path.GetDirectoryName(invocation.ProjectDirectory)) == "cycle-breakers")
194-
{
195-
Console.WriteLine($"Skipping Wpf Cycle-Breaker project {invocation.ProjectFilePath}");
196-
return false;
197-
}
198-
return true;
199-
})
208+
.Where(invocation => !ShouldExcludeInvocation(invocation))
200209
.GroupBy(invocation => invocation.AssemblyName)
201210
.Select(group => SelectBestInvocation(group));
202211

@@ -217,6 +226,11 @@ static void Main(string[] args)
217226
{
218227
continue;
219228
}
229+
230+
if (!processed.Add(Path.GetFileNameWithoutExtension(invocation.ProjectFilePath)))
231+
{
232+
continue;
233+
}
220234
Console.WriteLine($"Converting Project: {invocation.ProjectFilePath}");
221235

222236
string repoRelativeProjectPath = Path.GetRelativePath(repoRoot, invocation.ProjectFilePath);

0 commit comments

Comments
 (0)