Skip to content

Commit

Permalink
Pulled out IAssemblyGenerator and moved more logic to JasperFx from J…
Browse files Browse the repository at this point in the history
…asperFx.RuntimeCompiler. Closes GH-17
  • Loading branch information
jeremydmiller committed Nov 12, 2024
1 parent adf72ac commit c3caa32
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/JasperFx.RuntimeCompiler/AssemblyGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace JasperFx.RuntimeCompiler
/// <summary>
/// Use to compile C# code to in memory assemblies using the Roslyn compiler
/// </summary>
public class AssemblyGenerator
public class AssemblyGenerator : IAssemblyGenerator
{

private readonly IList<MetadataReference> _references = new List<MetadataReference>();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
using System;
using System.IO;
using System.Threading.Tasks;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace JasperFx.RuntimeCompiler
namespace JasperFx.CodeGeneration
{
public static class CodeFileExtensions
{
public static async Task Initialize(this ICodeFile file, GenerationRules rules, ICodeFileCollection parent, IServiceProvider? services)
public static async Task Initialize(this ICodeFile file, GenerationRules rules, ICodeFileCollection parent, IServiceProvider services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

var @namespace = parent.ToNamespace(rules);

if (rules.TypeLoadMode == TypeLoadMode.Dynamic)
Expand All @@ -22,8 +24,8 @@ public static async Task Initialize(this ICodeFile file, GenerationRules rules,
var generatedAssembly = parent.StartAssembly(rules);
file.AssembleTypes(generatedAssembly);
var serviceVariables = parent is ICodeFileCollectionWithServices ? services?.GetService(typeof(IServiceVariableSource)) as IServiceVariableSource : null;
var compiler = new AssemblyGenerator();

var compiler = services.GetRequiredService<IAssemblyGenerator>();
compiler.Compile(generatedAssembly, serviceVariables);
await file.AttachTypes(rules, generatedAssembly.Assembly!, services, @namespace);

Expand All @@ -47,9 +49,9 @@ public static async Task Initialize(this ICodeFile file, GenerationRules rules,
var generatedAssembly = parent.StartAssembly(rules);
file.AssembleTypes(generatedAssembly);
var serviceVariables = services?.GetService(typeof(IServiceVariableSource)) as IServiceVariableSource;
var compiler = new AssemblyGenerator();


var compiler = services.GetRequiredService<IAssemblyGenerator>();
compiler.Compile(generatedAssembly, serviceVariables);

await file.AttachTypes(rules, generatedAssembly.Assembly!, services, @namespace);
Expand All @@ -75,9 +77,14 @@ public static async Task Initialize(this ICodeFile file, GenerationRules rules,
/// <param name="parent"></param>
/// <param name="services"></param>
/// <exception cref="ExpectedTypeMissingException"></exception>
public static void InitializeSynchronously(this ICodeFile file, GenerationRules rules, ICodeFileCollection parent, IServiceProvider? services)
public static void InitializeSynchronously(this ICodeFile file, GenerationRules rules, ICodeFileCollection parent, IServiceProvider services)
{
var logger = services?.GetService(typeof(ILogger<AssemblyGenerator>)) as ILogger ?? NullLogger.Instance;
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}

var logger = services?.GetService(typeof(ILogger<IAssemblyGenerator>)) as ILogger ?? NullLogger.Instance;
var @namespace = parent.ToNamespace(rules);

if (rules.TypeLoadMode == TypeLoadMode.Dynamic)
Expand All @@ -90,8 +97,8 @@ public static void InitializeSynchronously(this ICodeFile file, GenerationRules
var generatedAssembly = parent.StartAssembly(rules);
file.AssembleTypes(generatedAssembly);
var serviceVariables = parent is ICodeFileCollectionWithServices ? services?.GetService(typeof(IServiceVariableSource)) as IServiceVariableSource : null;
var compiler = new AssemblyGenerator();

var compiler = services.GetRequiredService<IAssemblyGenerator>();
compiler.Compile(generatedAssembly, serviceVariables);
file.AttachTypesSynchronously(rules, generatedAssembly.Assembly!, services, @namespace);

Expand All @@ -115,8 +122,8 @@ public static void InitializeSynchronously(this ICodeFile file, GenerationRules
var generatedAssembly = parent.StartAssembly(rules);
file.AssembleTypes(generatedAssembly);
var serviceVariables = services?.GetService(typeof(IServiceVariableSource)) as IServiceVariableSource;
var compiler = new AssemblyGenerator();

var compiler = services.GetRequiredService<IAssemblyGenerator>();
compiler.Compile(generatedAssembly, serviceVariables);

file.AttachTypesSynchronously(rules, generatedAssembly.Assembly!, services, @namespace);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System;

namespace JasperFx.RuntimeCompiler;
namespace JasperFx.CodeGeneration;

/// <summary>
/// Denotes an expected set of pre-built, generated types
Expand Down
40 changes: 40 additions & 0 deletions src/JasperFx/CodeGeneration/IAssemblyGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Reflection;
using JasperFx.CodeGeneration.Model;

namespace JasperFx.CodeGeneration;

public interface IAssemblyGenerator
{
string AssemblyName { get; set; }
string Code { get; }

/// <summary>
/// Tells Roslyn to reference the given assembly and any of its dependencies
/// when compiling code
/// </summary>
/// <param name="assembly"></param>
void ReferenceAssembly(Assembly assembly);

/// <summary>
/// Reference the assembly containing the type "T"
/// </summary>
/// <typeparam name="T"></typeparam>
void ReferenceAssemblyContainingType<T>();

/// <summary>
/// Compile code built up by using an ISourceWriter to a new assembly in memory
/// </summary>
/// <param name="write"></param>
/// <returns></returns>
Assembly Generate(Action<ISourceWriter> write);

/// <summary>
/// Compile the code passed into this method to a new assembly in memory
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
Assembly Generate(string code);

void Compile(GeneratedAssembly generatedAssembly, IServiceVariableSource services = null);
}
101 changes: 101 additions & 0 deletions src/JasperFx/Events/CodeGeneration/NewGeneratedProjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System.Reflection;
using JasperFx.CodeGeneration;
using JasperFx.Core.Reflection;

namespace JasperFx.Events.CodeGeneration;

/*
public abstract class NewGeneratedProjection<TOperations, TStore, TDatabase, TOptions> : ICodeFile
{
private readonly Type _projectionType;
private bool _hasGenerated;
public NewGeneratedProjection(Type projectionType)
{
_projectionType = projectionType;
}
internal TOptions StoreOptions { get; set; }
bool ICodeFile.AttachTypesSynchronously(GenerationRules rules, Assembly assembly, IServiceProvider services,
string containingNamespace)
{
return tryAttachTypes(assembly, StoreOptions);
}
public string FileName => GetType().ToSuffixedTypeName("RuntimeSupport");
void ICodeFile.AssembleTypes(GeneratedAssembly assembly)
{
if (_hasGenerated)
return;
lock (_assembleLocker)
{
if (_hasGenerated)
return;
assembleTypes(assembly, StoreOptions);
_hasGenerated = true;
}
}
Task<bool> ICodeFile.AttachTypes(GenerationRules rules, Assembly assembly, IServiceProvider services,
string containingNamespace)
{
var attached = tryAttachTypes(assembly, StoreOptions);
return Task.FromResult(attached);
}
public Type ProjectionType => GetType();
protected abstract void assembleTypes(GeneratedAssembly assembly, TOptions options);
protected abstract bool tryAttachTypes(Assembly assembly, TOptions options);
private void generateIfNecessary(TStore store)
{
lock (_assembleLocker)
{
if (_hasGenerated)
{
return;
}
generateIfNecessaryLocked();
_hasGenerated = true;
}
return;
void generateIfNecessaryLocked()
{
StoreOptions = store.Options;
var rules = store.Options.CreateGenerationRules();
rules.ReferenceTypes(GetType());
this.As<ICodeFile>().InitializeSynchronously(rules, store.Options.EventGraph, null);
if (!needsSettersGenerated())
{
return;
}
var generatedAssembly = new GeneratedAssembly(rules);
assembleTypes(generatedAssembly, store.Options);
// This will force it to create any setters or dynamic funcs
generatedAssembly.GenerateCode();
}
}
/// <summary>
/// Prevent code generation bugs when multiple aggregates are code generated in parallel
/// Happens more often on dynamic code generation
/// </summary>
protected static object _assembleLocker = new();
protected abstract bool needsSettersGenerated();
}
*/

0 comments on commit c3caa32

Please sign in to comment.