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