Skip to content
Open
1 change: 1 addition & 0 deletions build-tools/automation/azure-pipelines-public.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ stages:
buildResultArtifactName: Build Results - Linux
xaSourcePath: $(System.DefaultWorkingDirectory)/android
nugetArtifactName: $(LinuxNuGetArtifactName)
makeMSBuildArgs: -m:2
use1ESTemplate: false

# Package Tests Stage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ parameters:
buildResultArtifactName: Build Results - Linux
xaSourcePath: $(System.DefaultWorkingDirectory)/android
nugetArtifactName: $(LinuxNuGetArtifactName)
makeMSBuildArgs: ''
use1ESTemplate: true

steps:
Expand All @@ -26,7 +27,7 @@ steps:

- template: /build-tools/automation/yaml-templates/log-disk-space.yaml

- script: make jenkins PREPARE_CI=1 PREPARE_AUTOPROVISION=1 CONFIGURATION=$(XA.Build.Configuration)
- script: make jenkins PREPARE_CI=1 PREPARE_AUTOPROVISION=1 CONFIGURATION=$(XA.Build.Configuration) MSBUILD_ARGS='${{ parameters.makeMSBuildArgs }}'
workingDirectory: ${{ parameters.xaSourcePath }}
displayName: make jenkins

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// Helpers for parsing JNI method signatures.
/// </summary>
static class JniSignatureHelper
{
/// <summary>
/// Parses the raw JNI type descriptor strings from a JNI method signature.
/// </summary>
public static List<string> ParseParameterTypeStrings (string jniSignature)
{
var result = new List<string> ();
int i = 1; // skip opening '('
while (i < jniSignature.Length && jniSignature [i] != ')') {
int start = i;
SkipSingleType (jniSignature, ref i);
result.Add (jniSignature.Substring (start, i - start));
}
return result;
}

/// <summary>
/// Extracts the return type descriptor from a JNI method signature.
/// </summary>
public static string ParseReturnTypeString (string jniSignature)
{
int i = jniSignature.IndexOf (')') + 1;
return jniSignature.Substring (i);
}

static void SkipSingleType (string sig, ref int i)
{
switch (sig [i]) {
case 'V': case 'Z': case 'B': case 'C': case 'S':
case 'I': case 'J': case 'F': case 'D':
i++;
break;
case 'L':
i = sig.IndexOf (';', i) + 1;
break;
case '[':
i++;
SkipSingleType (sig, ref i);
break;
default:
throw new ArgumentException ($"Unknown JNI type character '{sig [i]}' in '{sig}' at index {i}");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.Security.Cryptography;
using System.Text;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

static class MetadataHelper
{
/// <summary>
/// Produces a deterministic MVID from the module name so that identical inputs produce identical assemblies.
/// </summary>
public static Guid DeterministicMvid (string moduleName)
{
using var sha = SHA256.Create ();
byte [] hash = sha.ComputeHash (Encoding.UTF8.GetBytes (moduleName));
byte [] guidBytes = new byte [16];
Array.Copy (hash, guidBytes, 16);
return new Guid (guidBytes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;

namespace Microsoft.Android.Sdk.TrimmableTypeMap;

/// <summary>
/// Data model for a single TypeMap output assembly.
/// Describes what to emit — the emitter writes this directly into a PE assembly.
/// Built by <see cref="ModelBuilder"/>, consumed by <see cref="TypeMapAssemblyGenerator"/>.
/// </summary>
sealed class TypeMapAssemblyData
{
/// <summary>
/// Assembly name (e.g., "_MyApp.TypeMap").
/// </summary>
public required string AssemblyName { get; init; }

/// <summary>

/// Module file name (e.g., "_MyApp.TypeMap.dll").

/// </summary>
public required string ModuleName { get; init; }

/// <summary>

/// TypeMap entries — one per unique JNI name.

/// </summary>
public List<TypeMapAttributeData> Entries { get; } = new ();

/// <summary>

/// Proxy types to emit in the assembly.

/// </summary>
public List<JavaPeerProxyData> ProxyTypes { get; } = new ();

/// <summary>

/// TypeMapAssociation entries for alias groups (multiple managed types → same JNI name).

/// </summary>
public List<TypeMapAssociationData> Associations { get; } = new ();

/// <summary>

/// Assembly names that need [IgnoresAccessChecksTo] for cross-assembly n_* calls.

/// </summary>
public List<string> IgnoresAccessChecksTo { get; } = new ();
}

/// <summary>
/// One [assembly: TypeMap("jni/name", typeof(Proxy))] or
/// [assembly: TypeMap("jni/name", typeof(Proxy), typeof(Target))] entry.
///
/// 2-arg (unconditional): proxy is always preserved — used for ACW types and essential runtime types.
/// 3-arg (trimmable): proxy is preserved only if Target type is referenced by the app.
/// </summary>
sealed record TypeMapAttributeData
{
/// <summary>
/// JNI type name, e.g., "android/app/Activity".
/// </summary>
public required string JniName { get; init; }

/// <summary>
/// Assembly-qualified proxy type reference string.
/// Either points to a generated proxy or to the original managed type.
/// </summary>
public required string ProxyTypeReference { get; init; }

/// <summary>
/// Assembly-qualified target type reference for the trimmable (3-arg) variant.
/// Null for unconditional (2-arg) entries.
/// The trimmer preserves the proxy only if this target type is used by the app.
/// </summary>
public string? TargetTypeReference { get; init; }

/// <summary>

/// True for 2-arg unconditional entries (ACW types, essential runtime types).

/// </summary>
public bool IsUnconditional => TargetTypeReference == null;
}

/// <summary>
/// A proxy type to generate in the TypeMap assembly (subclass of JavaPeerProxy).
/// </summary>
sealed class JavaPeerProxyData
{
/// <summary>
/// Simple type name, e.g., "Java_Lang_Object_Proxy".
/// </summary>
public required string TypeName { get; init; }

/// <summary>

/// Namespace for all proxy types.

/// </summary>
public string Namespace { get; init; } = "_TypeMap.Proxies";

/// <summary>

/// Reference to the managed type this proxy wraps (for ldtoken in TargetType property).

/// </summary>
public required TypeRefData TargetType { get; init; }

/// <summary>

/// Reference to the invoker type (for interfaces/abstract types). Null if not applicable.

/// </summary>
public TypeRefData? InvokerType { get; set; }

/// <summary>

/// Whether this proxy has a CreateInstance that can actually create instances.

/// </summary>
public bool HasActivation => ActivationCtor != null || InvokerType != null;

/// <summary>
/// Activation constructor details. Determines how CreateInstance instantiates the managed peer.
/// </summary>
public ActivationCtorData? ActivationCtor { get; set; }

/// <summary>

/// True if this is an open generic type definition. CreateInstance throws NotSupportedException.

/// </summary>
public bool IsGenericDefinition { get; init; }

}


/// <summary>
/// A cross-assembly type reference (assembly name + full managed type name).
/// </summary>
sealed record TypeRefData
{
/// <summary>
/// Full managed type name, e.g., "Android.App.Activity" or "MyApp.Outer+Inner".
/// </summary>
public required string ManagedTypeName { get; init; }

/// <summary>

/// Assembly containing the type, e.g., "Mono.Android".

/// </summary>
public required string AssemblyName { get; init; }
}

/// <summary>
/// Describes how the proxy's CreateInstance should construct the managed peer.
/// </summary>
sealed record ActivationCtorData
{
/// <summary>
/// Type that declares the activation constructor (may be a base type).
/// </summary>
public required TypeRefData DeclaringType { get; init; }

/// <summary>

/// True when the leaf type itself declares the activation ctor.

/// </summary>
public required bool IsOnLeafType { get; init; }

/// <summary>

/// The style of activation ctor (XamarinAndroid or JavaInterop).

/// </summary>
public required ActivationCtorStyle Style { get; init; }
}

/// <summary>
/// One [assembly: TypeMapAssociation(typeof(Source), typeof(AliasProxy))] entry.
/// Links a managed type to the proxy that holds its alias TypeMap entry.
/// </summary>
sealed record TypeMapAssociationData
{
/// <summary>
/// Assembly-qualified source type reference (the managed alias type).
/// </summary>
public required string SourceTypeReference { get; init; }

/// <summary>

/// Assembly-qualified proxy type reference (the alias holder proxy).

/// </summary>
public required string AliasProxyTypeReference { get; init; }
}
Loading