protected override void assembleTypes(GeneratedAssembly assembly, StoreOptions options) { assembly.Rules.ReferenceTypes(GetType()); assembly.ReferenceAssembly(GetType().Assembly); assembly.ReferenceAssembly(typeof(T).Assembly); assembly.Rules.ReferenceTypes(_applyMethods.ReferencedTypes().ToArray()); assembly.Rules.ReferenceTypes(_createMethods.ReferencedTypes().ToArray()); assembly.Rules.ReferenceTypes(_shouldDeleteMethods.ReferencedTypes().ToArray()); // Walk the assembly dependencies for the projection and aggregate types, // and this will catch generic type argument dependencies as well. For GH-2061 assembly.Rules.ReferenceTypes(GetType(), typeof(T)); assembly.UsingNamespaces.Add("System"); assembly.UsingNamespaces.Add("System.Linq"); _isAsync = _createMethods.IsAsync || _applyMethods.IsAsync; _aggregateMapping = options.Storage.FindMapping(typeof(T)); if (_aggregateMapping.IdMember == null) { throw new InvalidDocumentException( $"No identity property or field can be determined for the aggregate '{typeof(T).FullNameInCode()}', but one is required to be used as an aggregate in projections"); } buildLiveAggregationType(assembly); buildInlineAggregationType(assembly); }
public static (EventDocumentStorage, string) GenerateStorage(StoreOptions options) { var assembly = new GeneratedAssembly(new GenerationRules(SchemaConstants.MartenGeneratedNamespace)); assembly.ReferenceAssembly(typeof(EventGraph).Assembly); var builderType = assembly.AddType(EventDocumentStorageTypeName, typeof(EventDocumentStorage)); buildSelectorMethods(options, builderType); var appendType = buildAppendEventOperation(options.EventGraph, assembly); builderType.MethodFor(nameof(EventDocumentStorage.AppendEvent)) .Frames.Code($"return new Marten.Generated.AppendEventOperation(stream, e);"); var insertType = buildInsertStream(builderType, assembly, options.EventGraph); var streamQueryHandlerType = buildStreamQueryHandlerType(options.EventGraph, assembly); buildQueryForStreamMethod(options.EventGraph, builderType); var updateType = buildUpdateStreamVersion(builderType, assembly, options.EventGraph); var compiler = new AssemblyGenerator(); compiler.ReferenceAssembly(typeof(IMartenSession).Assembly); compiler.Compile(assembly); var writer = new StringWriter(); writer.WriteLine($" // {streamQueryHandlerType.TypeName}"); writer.WriteLine(streamQueryHandlerType.SourceCode); writer.WriteLine(); writer.WriteLine($" // {insertType.TypeName}"); writer.WriteLine(insertType.SourceCode); writer.WriteLine(); writer.WriteLine($" // {appendType.TypeName}"); writer.WriteLine(appendType.SourceCode); writer.WriteLine(); writer.WriteLine($" // {updateType.TypeName}"); writer.WriteLine(updateType.SourceCode); writer.WriteLine(); writer.WriteLine($" // {builderType.TypeName}"); writer.WriteLine(builderType.SourceCode); writer.WriteLine(); var code = writer.ToString(); var storage = (EventDocumentStorage)Activator.CreateInstance(builderType.CompiledType, options); return(storage, code); }
public void GenerateResolver(GeneratedAssembly generatedAssembly) { if (_resolverType != null) { return; // got some kind of loop in here we need to short circuit } if (ErrorMessages.Any() || Dependencies.SelectMany(x => x.ErrorMessages).Any()) { return; } var typeName = (ServiceType.FullNameInCode() + "_" + Name).Sanitize(); var buildType = ServiceType.MustBeBuiltWithFunc() || ImplementationType.MustBeBuiltWithFunc() ? typeof(object) : ServiceType; _resolverType = generatedAssembly.AddType(typeName, ResolverBaseType.MakeGenericType(buildType)); foreach (var relatedAssembly in relatedAssemblies()) { generatedAssembly.ReferenceAssembly(relatedAssembly); } var method = _resolverType.MethodFor("Build"); var frame = CreateBuildFrame(); method.Frames.Add(frame); }
public static EventDocumentStorage GenerateStorage(StoreOptions options) { var assembly = new GeneratedAssembly(new GenerationRules("Marten.Generated")); assembly.ReferenceAssembly(typeof(EventGraph).Assembly); var builderType = assembly.AddType("GeneratedEventDocumentStorage", typeof(EventDocumentStorage)); buildSelectorMethods(options, builderType); buildAppendEventOperation(options.Events, assembly); builderType.MethodFor(nameof(EventDocumentStorage.AppendEvent)) .Frames.Code($"return new Marten.Generated.AppendEventOperation(stream, e);"); buildInsertStream(builderType, assembly, options.Events); var streamQueryHandlerType = buildStreamQueryHandlerType(options.Events, assembly); buildQueryForStreamMethod(options.Events, builderType); buildUpdateStreamVersion(builderType, assembly, options.Events); var compiler = new AssemblyGenerator(); compiler.ReferenceAssembly(typeof(IMartenSession).Assembly); compiler.Compile(assembly); var code = streamQueryHandlerType.SourceCode; return((EventDocumentStorage)Activator.CreateInstance(builderType.CompiledType, options)); }
/// <summary> /// Generate a new method for the basic base class. The base class "TObject" should /// only have a single declared method /// </summary> /// <param name="configuration"></param> /// <param name="rules"></param> /// <typeparam name="TObject"></typeparam> /// <returns></returns> private static CodegenResult <TObject> ForBaseOf <TObject>(Action <GeneratedType, GeneratedMethod> configuration) { if (typeof(TObject).GetMethods().Length != 1) { throw new ArgumentOutOfRangeException(nameof(TObject), "The supplied base type or interface can only have exactly one declared method"); } var rules = Builder.Rules(); var assembly = new GeneratedAssembly(rules); var generatedType = assembly.AddType("Tests", "GeneratedType", typeof(TObject)); var method = generatedType.Methods.Single(); configuration(generatedType, method); if (typeof(TObject).IsGenericType) { foreach (var genericTypeArgument in typeof(TObject).GenericTypeArguments) { assembly.ReferenceAssembly(genericTypeArgument.Assembly); } } assembly.CompileAll(new InMemoryOnlyCompileStrategy(new NullLogger <InMemoryOnlyCompileStrategy>())); return(new CodegenResult <TObject>(generatedType.CreateInstance <TObject>(), generatedType.SourceCode)); }
public static GeneratedType AssembleTypes(StoreOptions options, GeneratedAssembly assembly) { assembly.ReferenceAssembly(typeof(EventGraph).Assembly); var builderType = assembly.AddType(EventDocumentStorageTypeName, typeof(EventDocumentStorage)); buildSelectorMethods(options, builderType); buildAppendEventOperation(options.EventGraph, assembly); builderType.MethodFor(nameof(EventDocumentStorage.AppendEvent)) .Frames.Code($"return new Marten.Generated.AppendEventOperation(stream, e);"); buildInsertStream(builderType, assembly, options.EventGraph); buildStreamQueryHandlerType(options.EventGraph, assembly); buildQueryForStreamMethod(options.EventGraph, builderType); buildUpdateStreamVersion(builderType, assembly, options.EventGraph); return(builderType); }
/// <summary> /// Given the specified <see cref="BlueprintApiOptions" /> will generate and compile an /// <see cref="IApiOperationExecutor" /> that can be used to execute any operation that /// has been identified by the model of the options passed. /// </summary> /// <param name="options">The configured options.</param> /// <param name="serviceProvider">The configured service provider.</param> /// <returns>An executor built from the given options and data model.</returns> public CodeGennedExecutor Build(BlueprintApiOptions options, IServiceProvider serviceProvider) { var model = options.Model; // We have multiple ways in which we work with generated assemblies, depending on context: // // - We are writing unit tests which create many pipelines (within Blueprint itself). Here we // would want to use in-memory compilation and assembly loading only // // - We have deployed an app using generated code. We want to use pre-compiled DLLs loaded as // part of the usual loading process. This is done by creating an assembly and PDB that is // deployed with the application and loaded below (see step 1) // // - We are in development. Here we wish to generate and load a new DLL on application startup and // store in the temp folder of the machine. This means the DLL is _not_ loaded as normal part // of .NET process and therefore we can (re)create at will on startup without worrying about // the existence of an existing DLL // 1. Try and find an already loaded assembly foreach (var loadedAssembly in AppDomain.CurrentDomain.GetAssemblies()) { if (loadedAssembly.GetName().Name == options.GenerationRules.AssemblyName) { // The assembly exists in the current domain, therefore it has either already been generated in this // process OR it has previously been compiled and loaded as part of normal assembly loading (pre-compiled // as part of dotnet publish) this._logger.LogInformation("Assembly {AssemblyName} already exists, using to create executor.", options.GenerationRules.AssemblyName); return(CreateFromAssembly(options, serviceProvider, model, loadedAssembly)); } } // 2. Do we have the DLL stored alongside this application but NOT loaded? var directory = Path.GetDirectoryName(typeof(ApiOperationExecutorBuilder).Assembly.Location); var assemblyPath = Path.Combine(directory, options.GenerationRules.AssemblyName) + ".dll"; if (File.Exists(assemblyPath)) { this._logger.LogInformation( "Assembly {AssemblyName} found at {AssemblyLocation}. Loading and using to create executor.", options.GenerationRules.AssemblyName, assemblyPath); var loadedPipelineDll = AssemblyLoadContext.Default.LoadFromAssemblyPath(assemblyPath); return(CreateFromAssembly(options, serviceProvider, model, loadedPipelineDll)); } // 2. We DO NOT have any existing DLLs. In that case we are going to generate the source code using our configured // middlewares and then hand off to AssemblyGenerator to compile and load the assembly (which may be in-memory, stored // to a temp folder or stored to the project output folder) this._logger.LogInformation("Building Blueprint API operation executor for {0} operations", options.Model.Operations.Count()); using var serviceScope = serviceProvider.CreateScope(); foreach (var middleware in options.MiddlewareBuilders) { this.Use(middleware); } var typeToCreationMappings = new Dictionary <Type, Func <Type> >(); // Start the definition for a new generated assembly var assembly = new GeneratedAssembly(options.GenerationRules); foreach (var operation in model.Operations) { this._references.Add(operation.OperationType.Assembly); } foreach (var a in this._references) { this._logger.LogDebug("Referencing assembly {0}", a.FullName); assembly.ReferenceAssembly(a); } foreach (var operation in model.Operations) { this._logger.LogDebug("Generating executor for {0}", operation.OperationType.FullName); var typeName = NormaliseTypeName(operation); var pipelineExecutorType = assembly.AddType( operation.OperationType.Namespace, typeName, typeof(IOperationExecutorPipeline)); // We need to set up a LoggerVariable once, to be shared between methods pipelineExecutorType.AllInjectedFields.Add(new LoggerVariable(typeName)); var executeMethod = pipelineExecutorType.MethodFor(nameof(IOperationExecutorPipeline.ExecuteAsync)); var executeNestedMethod = pipelineExecutorType.MethodFor(nameof(IOperationExecutorPipeline.ExecuteNestedAsync)); this.Generate(options, serviceProvider, executeMethod, operation, model, serviceScope, false); this.Generate(options, serviceProvider, executeNestedMethod, operation, model, serviceScope, true); typeToCreationMappings.Add( operation.OperationType, () => pipelineExecutorType.CompiledType); } this._logger.LogInformation("Compiling {0} pipeline executors", typeToCreationMappings.Count); assembly.CompileAll(serviceProvider.GetRequiredService <ICompileStrategy>()); this._logger.LogInformation("Done compiling {0} pipeline executors", typeToCreationMappings.Count); return(new CodeGennedExecutor( serviceProvider, model, assembly, typeToCreationMappings)); }