/// <summary> /// Generates a syntax tree for the provided assemblies. /// </summary> /// <param name="targetAssembly">The assemblies used for accessiblity checks, or <see langword="null"/> during runtime code generation.</param> /// <param name="assemblies">The assemblies to generate code for.</param> /// <returns>The generated syntax tree.</returns> private GeneratedSyntax GenerateCode(Assembly targetAssembly, List <Assembly> assemblies) { var features = new FeatureDescriptions(); var members = new List <MemberDeclarationSyntax>(); // Expand the list of included assemblies and types. var knownAssemblies = new Dictionary <Assembly, KnownAssemblyAttribute>( assemblies.ToDictionary(k => k, k => default(KnownAssemblyAttribute))); foreach (var attribute in assemblies.SelectMany(asm => asm.GetCustomAttributes <KnownAssemblyAttribute>())) { knownAssemblies[attribute.Assembly] = attribute; } if (logger.IsEnabled(LogLevel.Information)) { logger.Info($"Generating code for assemblies: {string.Join(", ", knownAssemblies.Keys.Select(a => a.FullName))}"); } // Get types from assemblies which reference Orleans and are not generated assemblies. var grainClasses = new HashSet <Type>(); var grainInterfaces = new HashSet <Type>(); foreach (var pair in knownAssemblies) { var assembly = pair.Key; var treatTypesAsSerializable = pair.Value?.TreatTypesAsSerializable ?? false; foreach (var type in TypeUtils.GetDefinedTypes(assembly, this.logger)) { if (treatTypesAsSerializable || type.IsSerializable || TypeHasKnownBase(type)) { string logContext = null; if (logger.IsEnabled(LogLevel.Trace)) { if (treatTypesAsSerializable) { logContext = $"known assembly {assembly.GetName().Name} where 'TreatTypesAsSerializable' = true"; } else if (type.IsSerializable) { logContext = $"known assembly {assembly.GetName().Name} where type is [Serializable]"; } else if (type.IsSerializable) { logContext = $"known assembly {assembly.GetName().Name} where type has known base type."; } } serializableTypes.RecordType(type, targetAssembly, logContext); } // Include grain interfaces and classes. var isGrainInterface = GrainInterfaceUtils.IsGrainInterface(type); var isGrainClass = TypeUtils.IsConcreteGrainClass(type); if (isGrainInterface || isGrainClass) { // If code generation is being performed at runtime, the interface must be accessible to the generated code. if (!TypeUtilities.IsAccessibleFromAssembly(type, targetAssembly)) { if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.Debug("Skipping inaccessible grain type, {0}", type.GetParseableName()); } continue; } // Attempt to generate serializers for grain state classes, i.e, T in Grain<T>. var baseType = type.BaseType; if (baseType != null && baseType.IsConstructedGenericType) { foreach (var arg in baseType.GetGenericArguments()) { string logContext = null; if (logger.IsEnabled(LogLevel.Trace)) { logContext = "generic base type of " + type.GetLogFormat(); } this.serializableTypes.RecordType(arg, targetAssembly, logContext); } } // Skip classes generated by this generator. if (IsOrleansGeneratedCode(type)) { if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.Debug("Skipping generated grain type, {0}", type.GetParseableName()); } continue; } if (this.knownGrainTypes.Contains(type)) { if (this.logger.IsEnabled(LogLevel.Debug)) { this.logger.Debug("Skipping grain type {0} since it already has generated code.", type.GetParseableName()); } continue; } if (isGrainClass) { if (this.logger.IsEnabled(LogLevel.Information)) { this.logger.Info("Found grain implementation class: {0}", type.GetParseableName()); } grainClasses.Add(type); } if (isGrainInterface) { if (this.logger.IsEnabled(LogLevel.Information)) { this.logger.Info("Found grain interface: {0}", type.GetParseableName()); } GrainInterfaceUtils.ValidateInterfaceRules(type); grainInterfaces.Add(type); } } } } // Group the types by namespace and generate the required code in each namespace. foreach (var groupedGrainInterfaces in grainInterfaces.GroupBy(_ => CodeGeneratorCommon.GetGeneratedNamespace(_))) { var namespaceName = groupedGrainInterfaces.Key; var namespaceMembers = new List <MemberDeclarationSyntax>(); foreach (var grainInterface in groupedGrainInterfaces) { var referenceTypeName = GrainReferenceGenerator.GetGeneratedClassName(grainInterface); var invokerTypeName = GrainMethodInvokerGenerator.GetGeneratedClassName(grainInterface); namespaceMembers.Add( GrainReferenceGenerator.GenerateClass( grainInterface, referenceTypeName, encounteredType => { string logContext = null; if (logger.IsEnabled(LogLevel.Trace)) { logContext = "used by grain type " + grainInterface.GetLogFormat(); } this.serializableTypes.RecordType(encounteredType, targetAssembly, logContext); })); namespaceMembers.Add(GrainMethodInvokerGenerator.GenerateClass(grainInterface, invokerTypeName)); var genericTypeSuffix = GetGenericTypeSuffix(grainInterface.GetGenericArguments().Length); features.GrainInterfaces.Add( new GrainInterfaceDescription { Interface = grainInterface.GetTypeSyntax(includeGenericParameters: false), Reference = SF.ParseTypeName(namespaceName + '.' + referenceTypeName + genericTypeSuffix), Invoker = SF.ParseTypeName(namespaceName + '.' + invokerTypeName + genericTypeSuffix), InterfaceId = GrainInterfaceUtils.GetGrainInterfaceId(grainInterface) }); } members.Add(CreateNamespace(namespaceName, namespaceMembers)); } foreach (var type in grainClasses) { features.GrainClasses.Add( new GrainClassDescription { ClassType = type.GetTypeSyntax(includeGenericParameters: false) }); } // Generate serializers into their own namespace. var serializerNamespace = this.GenerateSerializers(targetAssembly, features); members.Add(serializerNamespace); // Add serialization metadata for the types which were encountered. this.AddSerializationTypes(features.Serializers, targetAssembly, knownAssemblies.Keys.ToList()); foreach (var attribute in knownAssemblies.Keys.SelectMany(asm => asm.GetCustomAttributes <ConsiderForCodeGenerationAttribute>())) { this.serializableTypes.RecordType(attribute.Type, targetAssembly, "[ConsiderForCodeGeneration]"); if (attribute.ThrowOnFailure && !this.serializableTypes.IsTypeRecorded(attribute.Type) && !this.serializableTypes.IsTypeIgnored(attribute.Type)) { throw new CodeGenerationException( $"Found {attribute.GetType().Name} for type {attribute.Type.GetParseableName()}, but code" + " could not be generated. Ensure that the type is accessible."); } } // Generate metadata directives for all of the relevant types. var(attributeDeclarations, memberDeclarations) = FeaturePopulatorGenerator.GenerateSyntax(targetAssembly, features); members.AddRange(memberDeclarations); var compilationUnit = SF.CompilationUnit().AddAttributeLists(attributeDeclarations.ToArray()).AddMembers(members.ToArray()); return(new GeneratedSyntax { SourceAssemblies = knownAssemblies.Keys.ToList(), Syntax = compilationUnit }); }
/// <summary> /// Generates a syntax tree for the provided assemblies. /// </summary> /// <param name="assemblies">The assemblies to generate code for.</param> /// <param name="runtime">Whether or not runtime code generation is being performed.</param> /// <returns>The generated syntax tree.</returns> private GeneratedSyntax GenerateForAssemblies(Assembly targetAssembly, bool runtime) { var grainInterfaces = new List <GrainInterfaceDescription>(); var grainClasses = new List <GrainClassDescription>(); var serializationTypes = new SerializationTypeDescriptions(); var members = new List <MemberDeclarationSyntax>(); // Expand the list of included assemblies and types. var(includedTypes, assemblies) = this.GetIncludedTypes(targetAssembly, runtime); if (Logger.IsVerbose) { Logger.Verbose( "Generating code for assemblies: {0}", string.Join(", ", assemblies.Select(_ => _.FullName))); } var serializerNamespaceMembers = new List <MemberDeclarationSyntax>(); var serializerNamespaceName = $"{SerializerNamespacePrefix}{targetAssembly?.GetName().Name.GetHashCode():X}"; // Group the types by namespace and generate the required code in each namespace. foreach (var group in includedTypes.GroupBy(_ => CodeGeneratorCommon.GetGeneratedNamespace(_))) { var namespaceMembers = new List <MemberDeclarationSyntax>(); var namespaceName = group.Key; foreach (var type in group) { // Skip generated classes. if (type.GetCustomAttribute <GeneratedCodeAttribute>() != null) { continue; } // Every type which is encountered must be considered for serialization. void OnEncounteredType(Type encounteredType) { // If a type was encountered which can be accessed, process it for serialization. this.typeCollector.RecordEncounteredType(type); this.serializerGenerationManager.RecordTypeToGenerate(encounteredType, targetAssembly); } if (Logger.IsVerbose2) { Logger.Verbose2("Generating code for: {0}", type.GetParseableName()); } if (GrainInterfaceUtils.IsGrainInterface(type)) { if (Logger.IsVerbose2) { Logger.Verbose2( "Generating GrainReference and MethodInvoker for {0}", type.GetParseableName()); } GrainInterfaceUtils.ValidateInterfaceRules(type); var referenceTypeName = GrainReferenceGenerator.GetGeneratedClassName(type); var invokerTypeName = GrainMethodInvokerGenerator.GetGeneratedClassName(type); namespaceMembers.Add(GrainReferenceGenerator.GenerateClass(type, referenceTypeName, OnEncounteredType)); namespaceMembers.Add(GrainMethodInvokerGenerator.GenerateClass(type, invokerTypeName)); var genericTypeSuffix = GetGenericTypeSuffix(type.GetGenericArguments().Length); grainInterfaces.Add( new GrainInterfaceDescription { Interface = type.GetTypeSyntax(includeGenericParameters: false), Reference = SF.ParseTypeName(namespaceName + '.' + referenceTypeName + genericTypeSuffix), Invoker = SF.ParseTypeName(namespaceName + '.' + invokerTypeName + genericTypeSuffix), InterfaceId = GrainInterfaceUtils.GetGrainInterfaceId(type) }); } if (TypeUtils.IsConcreteGrainClass(type)) { grainClasses.Add( new GrainClassDescription { ClassType = type.GetTypeSyntax(includeGenericParameters: false) }); } // Generate serializers. var first = true; while (this.serializerGenerationManager.GetNextTypeToProcess(out var toGen)) { if (first) { Logger.Info("ClientGenerator - Generating serializer classes for types:"); first = false; } Logger.Info( "\ttype " + toGen.FullName + " in namespace " + toGen.Namespace + " defined in Assembly " + toGen.GetTypeInfo().Assembly.GetName()); if (Logger.IsVerbose2) { Logger.Verbose2( "Generating serializer for type {0}", toGen.GetParseableName()); } var generatedSerializerName = SerializerGenerator.GetGeneratedClassName(toGen); serializerNamespaceMembers.Add(SerializerGenerator.GenerateClass(generatedSerializerName, toGen, OnEncounteredType)); var qualifiedSerializerName = serializerNamespaceName + '.' + generatedSerializerName + GetGenericTypeSuffix(toGen.GetGenericArguments().Length); serializationTypes.SerializerTypes.Add( new SerializerTypeDescription { Serializer = SF.ParseTypeName(qualifiedSerializerName), Target = toGen.GetTypeSyntax(includeGenericParameters: false) }); } } if (namespaceMembers.Count == 0) { if (Logger.IsVerbose) { Logger.Verbose2("Skipping namespace: {0}", namespaceName); } continue; } members.Add(CreateNamespace(namespaceName, namespaceMembers)); } // Add all generated serializers to their own namespace. members.Add(CreateNamespace(serializerNamespaceName, serializerNamespaceMembers)); // Add serialization metadata for the types which were encountered. this.AddSerializationTypes(serializationTypes, targetAssembly); // Generate metadata directives for all of the relevant types. var(attributeDeclarations, memberDeclarations) = FeaturePopulatorGenerator.GenerateSyntax(targetAssembly, grainInterfaces, grainClasses, serializationTypes); members.AddRange(memberDeclarations); var compilationUnit = SF.CompilationUnit().AddAttributeLists(attributeDeclarations.ToArray()).AddMembers(members.ToArray()); return(new GeneratedSyntax { SourceAssemblies = assemblies, Syntax = compilationUnit }); string GetGenericTypeSuffix(int numParams) { if (numParams == 0) { return(string.Empty); } return('<' + new string(',', numParams - 1) + '>'); } NamespaceDeclarationSyntax CreateNamespace(string namespaceName, IEnumerable <MemberDeclarationSyntax> namespaceMembers) { return (SF.NamespaceDeclaration(SF.ParseName(namespaceName)) .AddUsings( TypeUtils.GetNamespaces(typeof(GrainExtensions), typeof(IntrospectionExtensions)) .Select(_ => SF.UsingDirective(SF.ParseName(_))) .ToArray()) .AddMembers(namespaceMembers.ToArray())); } }