diff --git a/eng/Versions.props b/eng/Versions.props index d87b6a2fe34..bf1e2b58feb 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -16,6 +16,7 @@ 11.0.100-preview.1.26076.102 0.11.5-preview.26076.102 9.0.4 + 11.0.0-preview.1.26104.118 36.1.30 $(MicrosoftNETSdkAndroidManifest100100PackageVersion) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/CompilerFeaturePolyfills.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/CompilerFeaturePolyfills.cs new file mode 100644 index 00000000000..6b5fc126876 --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/CompilerFeaturePolyfills.cs @@ -0,0 +1,28 @@ +// Polyfills for C# language features on netstandard2.0 + +// Required for init-only setters +namespace System.Runtime.CompilerServices +{ + static class IsExternalInit { } + + [AttributeUsage (AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + sealed class RequiredMemberAttribute : Attribute { } + + [AttributeUsage (AttributeTargets.All, AllowMultiple = true, Inherited = false)] + sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute (string featureName) + { + FeatureName = featureName; + } + + public string FeatureName { get; } + public bool IsOptional { get; init; } + } +} + +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage (AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] + sealed class SetsRequiredMembersAttribute : Attribute { } +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj new file mode 100644 index 00000000000..a8ba3b210e7 --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj @@ -0,0 +1,18 @@ + + + + + $(TargetFrameworkNETStandard) + enable + Nullable + Microsoft.Android.Sdk.TrimmableTypeMap + + + + + + + + + + diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs new file mode 100644 index 00000000000..0d12aa176fb --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/CustomAttributeTypeProvider.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Reflection.Metadata; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +/// +/// Minimal ICustomAttributeTypeProvider implementation for decoding +/// custom attribute values via System.Reflection.Metadata. +/// +sealed class CustomAttributeTypeProvider : ICustomAttributeTypeProvider +{ + readonly MetadataReader reader; + Dictionary? enumTypeCache; + + public CustomAttributeTypeProvider (MetadataReader reader) + { + this.reader = reader; + } + + public string GetPrimitiveType (PrimitiveTypeCode typeCode) => typeCode.ToString (); + + public string GetTypeFromDefinition (MetadataReader metadataReader, TypeDefinitionHandle handle, byte rawTypeKind) + { + var typeDef = metadataReader.GetTypeDefinition (handle); + var name = metadataReader.GetString (typeDef.Name); + if (typeDef.IsNested) { + var parent = GetTypeFromDefinition (metadataReader, typeDef.GetDeclaringType (), rawTypeKind); + return $"{parent}+{name}"; + } + var ns = metadataReader.GetString (typeDef.Namespace); + return ns.Length > 0 ? $"{ns}.{name}" : name; + } + + public string GetTypeFromReference (MetadataReader metadataReader, TypeReferenceHandle handle, byte rawTypeKind) + { + var typeRef = metadataReader.GetTypeReference (handle); + var name = metadataReader.GetString (typeRef.Name); + if (typeRef.ResolutionScope.Kind == HandleKind.TypeReference) { + var parent = GetTypeFromReference (metadataReader, (TypeReferenceHandle)typeRef.ResolutionScope, rawTypeKind); + return $"{parent}+{name}"; + } + var ns = metadataReader.GetString (typeRef.Namespace); + return ns.Length > 0 ? $"{ns}.{name}" : name; + } + + public string GetTypeFromSerializedName (string name) => name; + + public PrimitiveTypeCode GetUnderlyingEnumType (string type) + { + if (enumTypeCache == null) { + enumTypeCache = BuildEnumTypeCache (); + } + + if (enumTypeCache.TryGetValue (type, out var code)) { + return code; + } + + // Default to Int32 for enums defined in other assemblies + return PrimitiveTypeCode.Int32; + } + + Dictionary BuildEnumTypeCache () + { + var cache = new Dictionary (); + + foreach (var typeHandle in reader.TypeDefinitions) { + var typeDef = reader.GetTypeDefinition (typeHandle); + + // Only process enum types + if (!IsEnum (typeDef)) + continue; + + var fullName = GetTypeFromDefinition (reader, typeHandle, rawTypeKind: 0); + var code = GetEnumUnderlyingTypeCode (typeDef); + cache [fullName] = code; + } + + return cache; + } + + bool IsEnum (TypeDefinition typeDef) + { + var baseType = typeDef.BaseType; + if (baseType.IsNil) + return false; + + string? baseFullName = baseType.Kind switch { + HandleKind.TypeReference => GetTypeFromReference (reader, (TypeReferenceHandle)baseType, rawTypeKind: 0), + HandleKind.TypeDefinition => GetTypeFromDefinition (reader, (TypeDefinitionHandle)baseType, rawTypeKind: 0), + _ => null, + }; + + return baseFullName == "System.Enum"; + } + + PrimitiveTypeCode GetEnumUnderlyingTypeCode (TypeDefinition typeDef) + { + // For enums, the first instance field is the underlying value__ field + foreach (var fieldHandle in typeDef.GetFields ()) { + var field = reader.GetFieldDefinition (fieldHandle); + if ((field.Attributes & System.Reflection.FieldAttributes.Static) != 0) + continue; + + var sig = field.DecodeSignature (SignatureTypeProvider.Instance, genericContext: null); + return sig switch { + "System.Byte" => PrimitiveTypeCode.Byte, + "System.SByte" => PrimitiveTypeCode.SByte, + "System.Int16" => PrimitiveTypeCode.Int16, + "System.UInt16" => PrimitiveTypeCode.UInt16, + "System.Int32" => PrimitiveTypeCode.Int32, + "System.UInt32" => PrimitiveTypeCode.UInt32, + "System.Int64" => PrimitiveTypeCode.Int64, + "System.UInt64" => PrimitiveTypeCode.UInt64, + _ => PrimitiveTypeCode.Int32, + }; + } + + return PrimitiveTypeCode.Int32; + } + + public string GetSystemType () => "System.Type"; + + public string GetSZArrayType (string elementType) => $"{elementType}[]"; + + public bool IsSystemType (string type) => type == "System.Type" || type == "Type"; +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs new file mode 100644 index 00000000000..85472f1b3ba --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +/// +/// Represents a Java peer type discovered during assembly scanning. +/// Contains all data needed by downstream generators (TypeMap IL, UCO wrappers, JCW Java sources). +/// Generators consume this data model — they never touch PEReader/MetadataReader. +/// +sealed record JavaPeerInfo +{ + /// + /// JNI type name, e.g., "android/app/Activity". + /// Extracted from the [Register] attribute. + /// + public required string JavaName { get; init; } + + /// + /// Compat JNI type name, e.g., "myapp.namespace/MyType" for user types (uses raw namespace, not CRC64). + /// For MCW binding types (with [Register]), this equals . + /// Used by acw-map.txt to support legacy custom view name resolution in layout XMLs. + /// + public required string CompatJniName { get; init; } + + /// + /// Full managed type name, e.g., "Android.App.Activity". + /// + public required string ManagedTypeName { get; init; } + + /// + /// Assembly name the type belongs to, e.g., "Mono.Android". + /// + public required string AssemblyName { get; init; } + + /// + /// JNI name of the base Java type, e.g., "android/app/Activity" for a type + /// that extends Activity. Null for java/lang/Object or types without a Java base. + /// Needed by JCW Java source generation ("extends" clause). + /// + public string? BaseJavaName { get; init; } + + /// + /// JNI names of Java interfaces this type implements, e.g., ["android/view/View$OnClickListener"]. + /// Needed by JCW Java source generation ("implements" clause). + /// + public IReadOnlyList ImplementedInterfaceJavaNames { get; init; } = Array.Empty (); + + public bool IsInterface { get; init; } + public bool IsAbstract { get; init; } + + /// + /// If true, this is a Managed Callable Wrapper (MCW) binding type. + /// No JCW or RegisterNatives will be generated for it. + /// + public bool DoNotGenerateAcw { get; init; } + + /// + /// Types with component attributes ([Activity], [Service], etc.), + /// custom views from layout XML, or manifest-declared components + /// are unconditionally preserved (not trimmable). + /// + public bool IsUnconditional { get; init; } + + /// + /// Marshal methods: methods with [Register(name, sig, connector)], [Export], or + /// constructor registrations ([Register(".ctor", sig, "")] / [JniConstructorSignature]). + /// Constructors are identified by . + /// Ordered — the index in this list is the method's ordinal for RegisterNatives. + /// + public IReadOnlyList MarshalMethods { get; init; } = Array.Empty (); + + /// + /// Information about the activation constructor for this type. + /// May reference a base type's constructor if the type doesn't define its own. + /// + public ActivationCtorInfo? ActivationCtor { get; init; } + + /// + /// For interfaces and abstract types, the name of the invoker type + /// used to instantiate instances from Java. + /// + public string? InvokerTypeName { get; init; } + + /// + /// True if this is an open generic type definition. + /// Generic types get TypeMap entries but CreateInstance throws NotSupportedException. + /// + public bool IsGenericDefinition { get; init; } +} + +/// +/// Describes a marshal method (a method with [Register] or [Export]) on a Java peer type. +/// Contains all data needed to generate a UCO wrapper, a JCW native declaration, +/// and a RegisterNatives call. +/// +sealed record MarshalMethodInfo +{ + /// + /// JNI method name, e.g., "onCreate". + /// This is the Java method name (without n_ prefix). + /// + public required string JniName { get; init; } + + /// + /// JNI method signature, e.g., "(Landroid/os/Bundle;)V". + /// Contains both parameter types and return type. + /// + public required string JniSignature { get; init; } + + /// + /// The connector string from [Register], e.g., "GetOnCreate_Landroid_os_Bundle_Handler". + /// Null for [Export] methods. + /// + public string? Connector { get; init; } + + /// + /// Name of the managed method this maps to, e.g., "OnCreate". + /// + public required string ManagedMethodName { get; init; } + + /// + /// True if this is a constructor registration. + /// + public bool IsConstructor { get; init; } + + /// + /// For [Export] methods: Java exception types that the method declares it can throw. + /// Null for [Register] methods. + /// + public IReadOnlyList? ThrownNames { get; init; } + + /// + /// For [Export] methods: super constructor arguments string. + /// Null for [Register] methods. + /// + public string? SuperArgumentsString { get; init; } +} + +/// +/// Describes how to call the activation constructor for a Java peer type. +/// +sealed record ActivationCtorInfo +{ + /// + /// The type that declares the activation constructor. + /// May be the type itself or a base type. + /// + public required string DeclaringTypeName { get; init; } + + /// + /// The assembly containing the declaring type. + /// + public required string DeclaringAssemblyName { get; init; } + + /// + /// The style of activation constructor found. + /// + public required ActivationCtorStyle Style { get; init; } +} + +enum ActivationCtorStyle +{ + /// + /// Xamarin.Android style: (IntPtr handle, JniHandleOwnership transfer) + /// + XamarinAndroid, + + /// + /// Java.Interop style: (ref JniObjectReference reference, JniObjectReferenceOptions options) + /// + JavaInterop, +} diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs new file mode 100644 index 00000000000..24ff6045787 --- /dev/null +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/SignatureTypeProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Immutable; +using System.Reflection.Metadata; + +namespace Microsoft.Android.Sdk.TrimmableTypeMap; + +/// +/// Minimal ISignatureTypeProvider implementation for decoding method +/// signatures via System.Reflection.Metadata. +/// Returns fully qualified type name strings. +/// +sealed class SignatureTypeProvider : ISignatureTypeProvider +{ + public static readonly SignatureTypeProvider Instance = new (); + + public string GetPrimitiveType (PrimitiveTypeCode typeCode) => typeCode switch { + PrimitiveTypeCode.Void => "System.Void", + PrimitiveTypeCode.Boolean => "System.Boolean", + PrimitiveTypeCode.Char => "System.Char", + PrimitiveTypeCode.SByte => "System.SByte", + PrimitiveTypeCode.Byte => "System.Byte", + PrimitiveTypeCode.Int16 => "System.Int16", + PrimitiveTypeCode.UInt16 => "System.UInt16", + PrimitiveTypeCode.Int32 => "System.Int32", + PrimitiveTypeCode.UInt32 => "System.UInt32", + PrimitiveTypeCode.Int64 => "System.Int64", + PrimitiveTypeCode.UInt64 => "System.UInt64", + PrimitiveTypeCode.Single => "System.Single", + PrimitiveTypeCode.Double => "System.Double", + PrimitiveTypeCode.String => "System.String", + PrimitiveTypeCode.Object => "System.Object", + PrimitiveTypeCode.IntPtr => "System.IntPtr", + PrimitiveTypeCode.UIntPtr => "System.UIntPtr", + PrimitiveTypeCode.TypedReference => "System.TypedReference", + _ => typeCode.ToString (), + }; + + public string GetTypeFromDefinition (MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind) + { + var typeDef = reader.GetTypeDefinition (handle); + var ns = reader.GetString (typeDef.Namespace); + var name = reader.GetString (typeDef.Name); + if (typeDef.IsNested) { + var parent = GetTypeFromDefinition (reader, typeDef.GetDeclaringType (), rawTypeKind); + return $"{parent}+{name}"; + } + return ns.Length > 0 ? $"{ns}.{name}" : name; + } + + public string GetTypeFromReference (MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind) + { + var typeRef = reader.GetTypeReference (handle); + var name = reader.GetString (typeRef.Name); + + // Handle nested types: if the ResolutionScope is another TypeReference, resolve recursively + if (typeRef.ResolutionScope.Kind == HandleKind.TypeReference) { + var parent = GetTypeFromReference (reader, (TypeReferenceHandle)typeRef.ResolutionScope, rawTypeKind); + return $"{parent}+{name}"; + } + + var ns = reader.GetString (typeRef.Namespace); + return ns.Length > 0 ? $"{ns}.{name}" : name; + } + + public string GetTypeFromSpecification (MetadataReader reader, object? genericContext, TypeSpecificationHandle handle, byte rawTypeKind) + { + var typeSpec = reader.GetTypeSpecification (handle); + return typeSpec.DecodeSignature (this, genericContext); + } + + public string GetSZArrayType (string elementType) => $"{elementType}[]"; + public string GetArrayType (string elementType, ArrayShape shape) => $"{elementType}[{new string (',', shape.Rank - 1)}]"; + public string GetByReferenceType (string elementType) => $"{elementType}&"; + public string GetPointerType (string elementType) => $"{elementType}*"; + public string GetPinnedType (string elementType) => elementType; + public string GetModifiedType (string modifier, string unmodifiedType, bool isRequired) => unmodifiedType; + + public string GetGenericInstantiation (string genericType, ImmutableArray typeArguments) + { + return $"{genericType}<{string.Join (",", typeArguments)}>"; + } + + public string GetGenericTypeParameter (object? genericContext, int index) => $"!{index}"; + public string GetGenericMethodParameter (object? genericContext, int index) => $"!!{index}"; + + public string GetFunctionPointerType (MethodSignature signature) => "delegate*"; +}