/// <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 }); }
private NamespaceDeclarationSyntax GenerateSerializers(Assembly targetAssembly, FeatureDescriptions features) { var serializerNamespaceMembers = new List <MemberDeclarationSyntax>(); var serializerNamespaceName = $"{SerializerNamespacePrefix}{targetAssembly?.GetName().Name.GetHashCode():X}"; while (this.serializableTypes.GetNextTypeToProcess(out var toGen)) { if (this.logger.IsEnabled(LogLevel.Trace)) { this.logger.Trace("Generating serializer for type {0}", toGen.GetParseableName()); } var type = toGen; var generatedSerializerName = SerializerGenerator.GetGeneratedClassName(toGen); serializerNamespaceMembers.Add(SerializerGenerator.GenerateClass(generatedSerializerName, toGen, encounteredType => { string logContext = null; if (logger.IsEnabled(LogLevel.Trace)) { logContext = "generated serializer for " + type.GetLogFormat(); } this.serializableTypes.RecordType(encounteredType, targetAssembly, logContext); })); var qualifiedSerializerName = serializerNamespaceName + '.' + generatedSerializerName + GetGenericTypeSuffix(toGen.GetGenericArguments().Length); features.Serializers.SerializerTypes.Add( new SerializerTypeDescription { Serializer = SF.ParseTypeName(qualifiedSerializerName), Target = toGen.GetTypeSyntax(includeGenericParameters: false) }); } // Add all generated serializers to their own namespace. return(CreateNamespace(serializerNamespaceName, serializerNamespaceMembers)); }
public static (List <AttributeListSyntax>, List <MemberDeclarationSyntax>) GenerateSyntax(Assembly targetAssembly, FeatureDescriptions features) { var attributes = new List <AttributeListSyntax>(); var members = new List <MemberDeclarationSyntax>(); var className = CodeGeneratorCommon.ClassPrefix + Guid.NewGuid().ToString("N").Substring(0, 10) + ClassSuffix; // Generate a class for populating the metadata. var classSyntax = SF.ClassDeclaration(className) .AddBaseListTypes( SF.SimpleBaseType(typeof(IFeaturePopulator <GrainInterfaceFeature>).GetTypeSyntax()), SF.SimpleBaseType(typeof(IFeaturePopulator <GrainClassFeature>).GetTypeSyntax()), SF.SimpleBaseType(typeof(IFeaturePopulator <SerializerFeature>).GetTypeSyntax())) .AddModifiers(SF.Token(SyntaxKind.InternalKeyword), SF.Token(SyntaxKind.SealedKeyword)) .AddMembers(GeneratePopulateMethod(features.GrainInterfaces), GeneratePopulateMethod(features.GrainClasses), GeneratePopulateMethod(features.Serializers)) .AddAttributeLists(SF.AttributeList(SF.SingletonSeparatedList(CodeGeneratorCommon.GetGeneratedCodeAttributeSyntax()))); var namespaceSyntax = SF.NamespaceDeclaration(NamespaceName.ToIdentifierName()).AddMembers(classSyntax); members.Add(namespaceSyntax); // Generate an assembly-level attribute with an instance of that class. var attribute = SF.AttributeList( SF.AttributeTargetSpecifier(SF.Token(SyntaxKind.AssemblyKeyword)), SF.SingletonSeparatedList( SF.Attribute(typeof(FeaturePopulatorAttribute).GetNameSyntax()) .AddArgumentListArguments(SF.AttributeArgument(SF.TypeOfExpression(SF.ParseTypeName(NamespaceName + "." + className)))))); attributes.Add(attribute); return(attributes, members); }