Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<MicrosoftTemplateEngineAuthoringTasksPackageVersion>11.0.100-preview.1.26076.102</MicrosoftTemplateEngineAuthoringTasksPackageVersion>
<MicrosoftDotNetCecilPackageVersion>0.11.5-preview.26076.102</MicrosoftDotNetCecilPackageVersion>
<SystemIOHashingPackageVersion>9.0.4</SystemIOHashingPackageVersion>
<SystemReflectionMetadataPackageVersion>11.0.0-preview.1.26104.118</SystemReflectionMetadataPackageVersion>
<!-- Previous .NET Android version -->
<MicrosoftNETSdkAndroidManifest100100PackageVersion>36.1.30</MicrosoftNETSdkAndroidManifest100100PackageVersion>
<AndroidNetPreviousVersion>$(MicrosoftNETSdkAndroidManifest100100PackageVersion)</AndroidNetPreviousVersion>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\Configuration.props" />

<PropertyGroup>
<TargetFramework>$(TargetFrameworkNETStandard)</TargetFramework>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<RootNamespace>Microsoft.Android.Sdk.TrimmableTypeMap</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.IO.Hashing" Version="$(SystemIOHashingPackageVersion)" />
<PackageReference Include="System.Reflection.Metadata" Version="$(SystemReflectionMetadataPackageVersion)" />
<InternalsVisibleTo Include="Microsoft.Android.Sdk.TrimmableTypeMap.Tests" />
<InternalsVisibleTo Include="Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Reflection.Metadata;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// Minimal ICustomAttributeTypeProvider implementation for decoding
/// custom attribute values via System.Reflection.Metadata.
/// </summary>
sealed class CustomAttributeTypeProvider : ICustomAttributeTypeProvider<string>
{
readonly MetadataReader reader;
Dictionary<string, PrimitiveTypeCode>? 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<string, PrimitiveTypeCode> BuildEnumTypeCache ()
{
var cache = new Dictionary<string, PrimitiveTypeCode> ();

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";
}
173 changes: 173 additions & 0 deletions src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
using System;
using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// 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.
/// </summary>
sealed record JavaPeerInfo
{
/// <summary>
/// JNI type name, e.g., "android/app/Activity".
/// Extracted from the [Register] attribute.
/// </summary>
public required string JavaName { get; init; }

/// <summary>
/// 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 <see cref="JavaName"/>.
/// Used by acw-map.txt to support legacy custom view name resolution in layout XMLs.
/// </summary>
public required string CompatJniName { get; init; }

/// <summary>
/// Full managed type name, e.g., "Android.App.Activity".
/// </summary>
public required string ManagedTypeName { get; init; }

/// <summary>
/// Assembly name the type belongs to, e.g., "Mono.Android".
/// </summary>
public required string AssemblyName { get; init; }

/// <summary>
/// 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).
/// </summary>
public string? BaseJavaName { get; init; }

/// <summary>
/// JNI names of Java interfaces this type implements, e.g., ["android/view/View$OnClickListener"].
/// Needed by JCW Java source generation ("implements" clause).
/// </summary>
public IReadOnlyList<string> ImplementedInterfaceJavaNames { get; init; } = Array.Empty<string> ();

public bool IsInterface { get; init; }
public bool IsAbstract { get; init; }

/// <summary>
/// If true, this is a Managed Callable Wrapper (MCW) binding type.
/// No JCW or RegisterNatives will be generated for it.
/// </summary>
public bool DoNotGenerateAcw { get; init; }

/// <summary>
/// Types with component attributes ([Activity], [Service], etc.),
/// custom views from layout XML, or manifest-declared components
/// are unconditionally preserved (not trimmable).
/// </summary>
public bool IsUnconditional { get; init; }

/// <summary>
/// Marshal methods: methods with [Register(name, sig, connector)], [Export], or
/// constructor registrations ([Register(".ctor", sig, "")] / [JniConstructorSignature]).
/// Constructors are identified by <see cref="MarshalMethodInfo.IsConstructor"/>.
/// Ordered — the index in this list is the method's ordinal for RegisterNatives.
/// </summary>
public IReadOnlyList<MarshalMethodInfo> MarshalMethods { get; init; } = Array.Empty<MarshalMethodInfo> ();

/// <summary>
/// Information about the activation constructor for this type.
/// May reference a base type's constructor if the type doesn't define its own.
/// </summary>
public ActivationCtorInfo? ActivationCtor { get; init; }

/// <summary>
/// For interfaces and abstract types, the name of the invoker type
/// used to instantiate instances from Java.
/// </summary>
public string? InvokerTypeName { get; init; }

/// <summary>
/// True if this is an open generic type definition.
/// Generic types get TypeMap entries but CreateInstance throws NotSupportedException.
/// </summary>
public bool IsGenericDefinition { get; init; }
}

/// <summary>
/// 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.
/// </summary>
sealed record MarshalMethodInfo
{
/// <summary>
/// JNI method name, e.g., "onCreate".
/// This is the Java method name (without n_ prefix).
/// </summary>
public required string JniName { get; init; }

/// <summary>
/// JNI method signature, e.g., "(Landroid/os/Bundle;)V".
/// Contains both parameter types and return type.
/// </summary>
public required string JniSignature { get; init; }

/// <summary>
/// The connector string from [Register], e.g., "GetOnCreate_Landroid_os_Bundle_Handler".
/// Null for [Export] methods.
/// </summary>
public string? Connector { get; init; }

/// <summary>
/// Name of the managed method this maps to, e.g., "OnCreate".
/// </summary>
public required string ManagedMethodName { get; init; }

/// <summary>
/// True if this is a constructor registration.
/// </summary>
public bool IsConstructor { get; init; }

/// <summary>
/// For [Export] methods: Java exception types that the method declares it can throw.
/// Null for [Register] methods.
/// </summary>
public IReadOnlyList<string>? ThrownNames { get; init; }

/// <summary>
/// For [Export] methods: super constructor arguments string.
/// Null for [Register] methods.
/// </summary>
public string? SuperArgumentsString { get; init; }
}

/// <summary>
/// Describes how to call the activation constructor for a Java peer type.
/// </summary>
sealed record ActivationCtorInfo
{
/// <summary>
/// The type that declares the activation constructor.
/// May be the type itself or a base type.
/// </summary>
public required string DeclaringTypeName { get; init; }

/// <summary>
/// The assembly containing the declaring type.
/// </summary>
public required string DeclaringAssemblyName { get; init; }

/// <summary>
/// The style of activation constructor found.
/// </summary>
public required ActivationCtorStyle Style { get; init; }
}

enum ActivationCtorStyle
{
/// <summary>
/// Xamarin.Android style: (IntPtr handle, JniHandleOwnership transfer)
/// </summary>
XamarinAndroid,

/// <summary>
/// Java.Interop style: (ref JniObjectReference reference, JniObjectReferenceOptions options)
/// </summary>
JavaInterop,
}
Loading
Loading