2023-11-03 10:56:23 +00:00
namespace BizHawk.SrcGen.ReflectionCache ;
2021-04-03 23:00:43 +00:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using Microsoft.CodeAnalysis ;
using Microsoft.CodeAnalysis.CSharp.Syntax ;
using Microsoft.CodeAnalysis.Text ;
2023-11-03 10:56:23 +00:00
[Generator]
public sealed class ReflectionCacheGenerator : ISourceGenerator
2021-04-03 23:00:43 +00:00
{
2023-11-03 10:56:23 +00:00
private sealed class ReflectionCacheGenSyntaxReceiver : ISyntaxReceiver
2021-04-03 23:00:43 +00:00
{
2023-11-03 10:56:23 +00:00
/// <remarks>
/// I may have just added RNG to the build process...
/// Increase this sample size to decrease chance of random failure.
/// Alternatively, if you can come up with a better way of getting the project name in <see cref="ISourceGenerator.Execute"/> (I tried like 5 different things), do that instead.
/// --yoshi
/// </remarks>
private const int SAMPLE_SIZE = 20 ;
2021-04-03 23:00:43 +00:00
2023-11-03 10:56:23 +00:00
private string? _namespace ;
2021-04-03 23:00:43 +00:00
2023-11-03 10:56:23 +00:00
private readonly List < string > _namespaces = new ( ) ;
2021-04-03 23:00:43 +00:00
2023-11-03 10:56:23 +00:00
public string Namespace = > _namespace ? ? = CalcNamespace ( ) ;
2021-04-03 23:00:43 +00:00
2023-11-03 10:56:23 +00:00
private string CalcNamespace ( )
{
// black magic wizardry to find common prefix https://stackoverflow.com/a/35081977
var ns = new string ( _namespaces [ 0 ]
. Substring ( 0 , _namespaces . Min ( s = > s . Length ) )
. TakeWhile ( ( c , i ) = > _namespaces . TrueForAll ( s = > s [ i ] = = c ) )
. ToArray ( ) ) ;
return ns [ ns . Length - 1 ] = = '.' ? ns . Substring ( 0 , ns . Length - 1 ) : ns ; // trim trailing '.' (can't use BizHawk.Common from Source Generators)
}
public void OnVisitSyntaxNode ( SyntaxNode syntaxNode )
{
static string Ser ( NameSyntax nameSyn ) = > nameSyn switch
2021-04-03 23:00:43 +00:00
{
2023-11-03 10:56:23 +00:00
SimpleNameSyntax simple = > simple . Identifier . ValueText ,
QualifiedNameSyntax qual = > $"{Ser(qual.Left)}.{Ser(qual.Right)}" ,
_ = > throw new InvalidOperationException ( )
} ;
if ( _namespace ! = null | | syntaxNode is not NamespaceDeclarationSyntax syn ) return ;
var newNS = Ser ( syn . Name ) ;
if ( ! newNS . StartsWith ( "BizHawk." , StringComparison . Ordinal ) ) return ;
_namespaces . Add ( newNS ) ;
if ( _namespaces . Count = = SAMPLE_SIZE ) _namespace = CalcNamespace ( ) ;
2021-04-03 23:00:43 +00:00
}
2023-11-03 10:56:23 +00:00
}
2021-04-03 23:00:43 +00:00
2023-11-03 10:56:23 +00:00
public void Initialize ( GeneratorInitializationContext context )
= > context . RegisterForSyntaxNotifications ( ( ) = > new ReflectionCacheGenSyntaxReceiver ( ) ) ;
2021-04-03 23:00:43 +00:00
2023-11-03 10:56:23 +00:00
public void Execute ( GeneratorExecutionContext context )
{
if ( context . SyntaxReceiver is not ReflectionCacheGenSyntaxReceiver receiver ) return ;
var nSpace = receiver . Namespace ;
var src = $ @ "#nullable enable
2021-04-03 23:00:43 +00:00
using System ;
2021-11-27 17:29:47 +00:00
using System.Collections.Generic ;
2021-04-03 23:00:43 +00:00
using System.IO ;
using System.Linq ;
using System.Reflection ;
2021-11-27 17:22:20 +00:00
{ ( nSpace = = "BizHawk.Common" ? string . Empty : "\nusing BizHawk.Common;" ) }
2021-11-27 17:29:47 +00:00
using BizHawk.Common.StringExtensions ;
2021-04-03 23:00:43 +00:00
namespace { nSpace }
{ {
public static class ReflectionCache
{ {
2021-11-27 17:29:47 +00:00
private const string EMBED_PREFIX = "" { nSpace } . "" ;
2024-05-29 19:39:57 +00:00
private static Type [ ] ? _types = null ;
2021-11-27 17:22:20 +00:00
2021-04-03 23:00:43 +00:00
private static readonly Assembly Asm = typeof ( { nSpace } . ReflectionCache ) . Assembly ;
2021-04-04 02:17:15 +00:00
public static readonly Version AsmVersion = Asm . GetName ( ) . Version ! ;
2021-11-27 17:22:20 +00:00
public static Type [ ] Types = > _types ? ? = Asm . GetTypesWithoutLoadErrors ( ) . ToArray ( ) ;
2021-04-03 23:00:43 +00:00
2021-11-27 17:29:47 +00:00
public static IEnumerable < string > EmbeddedResourceList ( string extraPrefix )
{ {
var fullPrefix = EMBED_PREFIX + extraPrefix ;
2024-05-29 19:39:57 +00:00
return Asm . GetManifestResourceNames ( ) . Where ( s = > s . StartsWithOrdinal ( fullPrefix ) ) // seems redundant with `RemovePrefix`, but we only want these in the final list
. Select ( s = > s . RemovePrefix ( fullPrefix ) ) ;
2021-11-27 17:29:47 +00:00
} }
public static IEnumerable < string > EmbeddedResourceList ( )
2024-05-29 19:39:57 +00:00
= > EmbeddedResourceList ( string . Empty ) ; // can't be simplified to `Asm.GetManifestResourceNames` call
2021-04-03 23:00:43 +00:00
/// <exception cref=""ArgumentException"">not found</exception>
public static Stream EmbeddedResourceStream ( string embedPath )
{ {
2021-11-27 17:29:47 +00:00
var fullPath = EMBED_PREFIX + embedPath ;
2024-05-29 19:39:57 +00:00
return Asm . GetManifestResourceStream ( fullPath )
? ? throw new ArgumentException ( paramName : nameof ( embedPath ) , message : $"" resource at { { fullPath } } not found "" ) ;
2021-04-03 23:00:43 +00:00
} }
} }
} }
";
2023-11-03 10:56:23 +00:00
context . AddSource ( "ReflectionCache.cs" , SourceText . From ( src , Encoding . UTF8 ) ) ;
2021-04-03 23:00:43 +00:00
}
}