Skip to content
Open
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
3 changes: 3 additions & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,9 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4316](xa4316.md): Specified input file '{file}' does not exist. Ignoring.
+ [XA4317](xa4317.md): Input file '{file}' does not start with `<replacements/>`. Skipping.
+ [XA4318](xa4318.md): Input file '{file}' could not be read: {message}. Skipping.
+ [XA4319](xa4319.md): No NativeAOT DGML files were provided.
+ [XA4320](xa4320.md): ACW map file '{file}' was not found.
+ [XA4321](xa4321.md): NativeAOT DGML file '{file}' was not found.

## XA5xxx: GCC and toolchain

Expand Down
26 changes: 26 additions & 0 deletions Documentation/docs-mobile/messages/xa4319.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: .NET for Android error XA4319
description: XA4319 error code
ms.date: 05/27/2026
f1_keywords:
- "XA4319"
---

# .NET for Android error XA4319

## Example messages

```
error XA4319: No NativeAOT DGML files were provided.
```

## Issue

The .NET for Android build could not find any NativeAOT DGML files to use when
generating trimmable type map ProGuard configuration.

## Solution

This error is expected only for internal build state inconsistencies. Ensure the
project is building with the intended Runtime Identifier settings and that the
NativeAOT compile produced its DGML scan file.
25 changes: 25 additions & 0 deletions Documentation/docs-mobile/messages/xa4320.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
title: .NET for Android error XA4320
description: XA4320 error code
ms.date: 05/27/2026
f1_keywords:
- "XA4320"
---

# .NET for Android error XA4320

## Example messages

```
error XA4320: ACW map file '{file}' was not found.
```

## Issue

The .NET for Android build could not find the Android Callable Wrapper (ACW) map
file needed to generate trimmable type map ProGuard configuration.

## Solution

This error is expected only for internal build state inconsistencies. Rebuild
the project from a clean state so the ACW map is regenerated.
26 changes: 26 additions & 0 deletions Documentation/docs-mobile/messages/xa4321.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
title: .NET for Android error XA4321
description: XA4321 error code
ms.date: 05/27/2026
f1_keywords:
- "XA4321"
---

# .NET for Android error XA4321

## Example messages

```
error XA4321: NativeAOT DGML file '{file}' was not found.
```

## Issue

The .NET for Android build could not find a NativeAOT DGML scan file needed to
generate trimmable type map ProGuard configuration.

## Solution

This error is expected only for internal build state inconsistencies. Rebuild
the project from a clean state and verify the NativeAOT compile produced the
DGML scan file for the selected Runtime Identifier.
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ void MarkFrameworkArrayEntryPeers (IEnumerable<JavaPeerInfo> peers)
if (frameworkAssemblyNames.Contains (index.AssemblyName)) {
continue;
}
foreach (var frameworkAssemblyName in frameworkAssemblyNames) {
if (index.ReferencedTypeNamesByAssembly.TryGetValue (frameworkAssemblyName, out var typeNames)) {
referencedFrameworkTypes.UnionWith (typeNames);
foreach (var referencedTypeNames in index.ReferencedTypeNamesByAssembly) {
if (frameworkAssemblyNames.Contains (referencedTypeNames.Key)) {
referencedFrameworkTypes.UnionWith (referencedTypeNames.Value);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
<_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">mono.MonoRuntimeProvider</_TrimmableRuntimeProviderJavaName>
</PropertyGroup>

<!-- TODO: Generate a focused ProGuard configuration from the ILLink-trimmed typemap. https://github.com/dotnet/android/issues/11052 -->
<Target Name="_GenerateTrimmableTypeMapProguardConfiguration"
Comment thread
simonrozsival marked this conversation as resolved.
BeforeTargets="_CompileToDalvik"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' " />

<!-- Add TypeMap DLLs to ILLink input. ILLink natively understands TypeMapAttribute<T>. -->
<Target Name="_AddTrimmableTypeMapToLinker"
BeforeTargets="PrepareForILLink;_RunILLink"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,17 @@
Adds generated typemap assemblies to ILC inputs. -->
<Project>

<UsingTask TaskName="Xamarin.Android.Tasks.GenerateNativeAotProguardConfiguration" AssemblyFile="$(_XamarinAndroidBuildTasksAssembly)" />

<PropertyGroup>
<_TrimmableRuntimeProviderJavaName Condition=" '$(_TrimmableRuntimeProviderJavaName)' == '' ">net.dot.jni.nativeaot.NativeAotRuntimeProvider</_TrimmableRuntimeProviderJavaName>
<AndroidLinkTool Condition=" '$(AndroidLinkTool)' == '' ">r8</AndroidLinkTool>
<AndroidDexTool Condition=" '$(AndroidLinkTool)' == 'r8' ">d8</AndroidDexTool>
<AndroidEnableProguard Condition=" '$(AndroidLinkTool)' != '' ">True</AndroidEnableProguard>
<AndroidCreateProguardMappingFile Condition=" '$(AndroidCreateProguardMappingFile)' == '' and '$(AndroidLinkTool)' == 'r8' ">True</AndroidCreateProguardMappingFile>
<IlcGenerateDgmlFile Condition=" '$(AndroidLinkTool)' != '' and '$(IlcGenerateDgmlFile)' == '' ">true</IlcGenerateDgmlFile>
<_UseTrimmableNativeAotProguardConfiguration Condition=" '$(_UseTrimmableNativeAotProguardConfiguration)' == '' ">true</_UseTrimmableNativeAotProguardConfiguration>
<_CompileToDalvikDependsOnTargets>$(_CompileToDalvikDependsOnTargets);_GenerateTrimmableTypeMapProguardConfiguration</_CompileToDalvikDependsOnTargets>
</PropertyGroup>

<!-- Before ILC input computation, add generated assemblies to ILC inputs.
Expand All @@ -14,21 +23,49 @@
DependsOnTargets="_ReadGeneratedTrimmableTypeMapAssemblies">
<ItemGroup>
<_TrimmableTypeMapIlcAssemblies Include="@(_GeneratedTypeMapAssembliesFromList)" />
<_TrimmableTypeMapFrameworkIlcAssemblies Include="@(ResolvedFrameworkAssemblies->'$(_TypeMapOutputDirectory)_%(Filename).TypeMap.dll')" />
<_TrimmableTypeMapFrameworkIlcAssemblies Include="@(PrivateSdkAssemblies->'$(_TypeMapOutputDirectory)_%(Filename).TypeMap.dll')" />
<_TrimmableTypeMapFrameworkIlcAssemblies Include="@(ReferencePath->'$(_TypeMapOutputDirectory)_%(Filename).TypeMap.dll')"
<_TrimmableTypeMapFrameworkIlcAssemblyNames Include="@(ResolvedFrameworkAssemblies->'_%(Filename).TypeMap')" />
<_TrimmableTypeMapFrameworkIlcAssemblyNames Include="@(PrivateSdkAssemblies->'_%(Filename).TypeMap')" />
<_TrimmableTypeMapFrameworkIlcAssemblyNames Include="@(ReferencePath->'_%(Filename).TypeMap')"
Condition=" '%(ReferencePath.FrameworkAssembly)' == 'true' " />
<!-- The root assembly (_Microsoft.Android.TypeMaps) only contains TypeMapLoader.Initialize()
and assembly-level attributes — no types that need vtable generation.
Framework per-assembly typemap DLLs must remain IlcReference inputs so ILC can read
their conditional TypeMap attributes, but they must not be unmanaged-entrypoint roots. -->
<_TrimmableTypeMapUnmanagedEntryPointAssemblies Include="@(_TrimmableTypeMapIlcAssemblies)" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblies Remove="$(_TypeMapOutputDirectory)$(_TypeMapAssemblyName).dll" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblies Remove="$(_TypeMapOutputDirectory)_Java.Interop.TypeMap.dll;$(_TypeMapOutputDirectory)_Mono.Android.TypeMap.dll" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblies Remove="@(_TrimmableTypeMapFrameworkIlcAssemblies)" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblyNames Include="@(_TrimmableTypeMapIlcAssemblies->'%(Filename)')" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblyNames Remove="$(_TypeMapAssemblyName)" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblyNames Remove="_Java.Interop.TypeMap;_Mono.Android.TypeMap" />
<_TrimmableTypeMapUnmanagedEntryPointAssemblyNames Remove="@(_TrimmableTypeMapFrameworkIlcAssemblyNames)" />
<IlcReference Include="@(_TrimmableTypeMapIlcAssemblies)" />
<IlcArg Include="--typemap-entry-assembly:$(_TypeMapAssemblyName)" />
<UnmanagedEntryPointsAssembly Include="@(_TrimmableTypeMapUnmanagedEntryPointAssemblies->'%(Filename)')" />
<UnmanagedEntryPointsAssembly Include="@(_TrimmableTypeMapUnmanagedEntryPointAssemblyNames)" />
</ItemGroup>
</Target>

<Target Name="_CollectTrimmableNativeAotDgmlFiles"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' ">
<ItemGroup>
<_TrimmableNativeAotRuntimeIdentifiers Remove="@(_TrimmableNativeAotRuntimeIdentifiers)" />
<_TrimmableNativeAotDgmlFiles Remove="@(_TrimmableNativeAotDgmlFiles)" />
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifier)" Condition=" '$(RuntimeIdentifier)' != '' " />
<_TrimmableNativeAotRuntimeIdentifiers Include="$(RuntimeIdentifiers)" Condition=" '$(RuntimeIdentifier)' == '' and '$(RuntimeIdentifiers)' != '' " />
<!-- RuntimeIdentifier, RuntimeIdentifiers, and no-RID publishes place ILC DGML under different intermediate paths. -->
<_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml" Condition=" '@(_TrimmableNativeAotRuntimeIdentifiers)' == '' " />
Comment thread
simonrozsival marked this conversation as resolved.
<_TrimmableNativeAotDgmlFiles Include="$(NativeIntermediateOutputPath)$(TargetName).scan.dgml.xml" Condition=" '$(RuntimeIdentifier)' != '' " />
<_TrimmableNativeAotDgmlFiles Include="$(IntermediateOutputPath)%(_TrimmableNativeAotRuntimeIdentifiers.Identity)\native\$(TargetName).scan.dgml.xml" Condition=" '$(RuntimeIdentifier)' == '' and '@(_TrimmableNativeAotRuntimeIdentifiers)' != '' " />
</ItemGroup>
</Target>

<Target Name="_GenerateTrimmableTypeMapProguardConfiguration"
DependsOnTargets="_CollectTrimmableNativeAotDgmlFiles"
Condition=" '$(PublishTrimmed)' == 'true' and '$(_ProguardProjectConfiguration)' != '' "
Inputs="@(_TrimmableNativeAotDgmlFiles);$(IntermediateOutputPath)acw-map.txt"
Outputs="$(_ProguardProjectConfiguration)">
<GenerateNativeAotProguardConfiguration
NativeAotDgmlFiles="@(_TrimmableNativeAotDgmlFiles)"
AcwMapFile="$(IntermediateOutputPath)acw-map.txt"
OutputFile="$(_ProguardProjectConfiguration)" />
<ItemGroup>
<FileWrites Include="$(_ProguardProjectConfiguration)" />
</ItemGroup>
</Target>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@
<Target Name="_ReadGeneratedTrimmableTypeMapAssemblies"
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' "
DependsOnTargets="_GenerateTrimmableTypeMap">
<MakeDir
Directories="$(_TypeMapOutputDirectory)"
Condition=" '@(ReferencePath->Count())' == '0' and '$(_OuterIntermediateOutputPath)' == '' and !Exists('$(_TypeMapAssembliesListFile)')" />
<Touch
Files="$(_TypeMapAssembliesListFile)"
AlwaysCreate="true"
Condition=" '@(ReferencePath->Count())' == '0' and '$(_OuterIntermediateOutputPath)' == '' and !Exists('$(_TypeMapAssembliesListFile)')" />
<Error
Condition=" !Exists('$(_TypeMapAssembliesListFile)') "
Text="Trimmable typemap assembly list '$(_TypeMapAssembliesListFile)' was not found." />
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,20 @@ Remove the '{0}' reference from your project and add the '{1}' NuGet package ins
<comment>{0} - The path to the file.
{1} - The exception message.
</comment>
</data>
<data name="XA4319" xml:space="preserve">
<value>No NativeAOT DGML files were provided.</value>
<comment>The following are literal names and should not be translated: NativeAOT, DGML</comment>
</data>
<data name="XA4320" xml:space="preserve">
<value>ACW map file '{0}' was not found.</value>
<comment>The following are literal names and should not be translated: ACW
{0} - The path to the ACW map file.</comment>
</data>
<data name="XA4321" xml:space="preserve">
<value>NativeAOT DGML file '{0}' was not found.</value>
<comment>The following are literal names and should not be translated: NativeAOT, DGML
{0} - The path to the NativeAOT DGML file.</comment>
</data>
<data name="XA5101" xml:space="preserve">
<value>Missing Android NDK toolchains directory '{0}'. Please install the Android NDK.</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Xamarin.Android NativeAOT trimmable typemap configuration.

-dontobfuscate

-keep class net.dot.jni.** { *; <init>(...); }
-keep class net.dot.android.crypto.** { *; <init>(...); }

-keepclassmembers class * extends android.view.View {
*** set*(...);
}

-keepclassmembers class * extends android.view.View {
<init>(android.content.Context,android.util.AttributeSet);
<init>(android.content.Context,android.util.AttributeSet,int);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#nullable enable

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Microsoft.Build.Framework;
using Microsoft.Android.Build.Tasks;

namespace Xamarin.Android.Tasks;

public class GenerateNativeAotProguardConfiguration : AndroidTask
{
const string TypeMetadataPrefix = "Type metadata: [";

public override string TaskPrefix => "GNAPC";

[Required]
public ITaskItem [] NativeAotDgmlFiles { get; set; } = [];

[Required]
public string AcwMapFile { get; set; } = "";

[Required]
public string OutputFile { get; set; } = "";

public override bool RunTask ()
{
var dir = Path.GetDirectoryName (OutputFile);
if (!dir.IsNullOrEmpty () && !Directory.Exists (dir)) {
Directory.CreateDirectory (dir);
}

if (NativeAotDgmlFiles.Length == 0) {
Log.LogCodedError ("XA4319", Properties.Resources.XA4319);
return !Log.HasLoggedErrors;
}
if (!File.Exists (AcwMapFile)) {
Log.LogCodedError ("XA4320", Properties.Resources.XA4320, AcwMapFile);
return !Log.HasLoggedErrors;
}
foreach (var dgmlFile in NativeAotDgmlFiles) {
if (!File.Exists (dgmlFile.ItemSpec)) {
Log.LogCodedError ("XA4321", Properties.Resources.XA4321, dgmlFile.ItemSpec);
return !Log.HasLoggedErrors;
}
}

var retainedTypeKeys = LoadRetainedTypeKeysFromDgml ();
var javaTypes = LoadJavaTypesFromAcwMap (retainedTypeKeys);

using var writer = new StringWriter ();
writer.WriteLine ("# ACWs retained by NativeAOT ILC");
foreach (var javaTypeName in javaTypes) {
writer.WriteLine ($"-keep class {javaTypeName} {{ *; }}");
}
Files.CopyIfStringChanged (writer.ToString (), OutputFile);

Log.LogMessage (MessageImportance.Low, "Generated {0} NativeAOT trimmable typemap ProGuard rules from {1} DGML file(s).", javaTypes.Count, NativeAotDgmlFiles.Length);
return !Log.HasLoggedErrors;
}

List<string> LoadJavaTypesFromAcwMap (HashSet<string> retainedTypeKeys)
{
var javaTypes = new List<string> (retainedTypeKeys.Count);
var seenJavaTypes = new HashSet<string> (StringComparer.Ordinal);
foreach (var line in File.ReadLines (AcwMapFile)) {
var separator = line.IndexOf (";", StringComparison.Ordinal);
if (separator <= 0 || separator == line.Length - 1) {
continue;
}
var managedTypeName = line.Substring (0, separator);
var javaTypeName = line.Substring (separator + 1);
if (retainedTypeKeys.Contains (managedTypeName) && seenJavaTypes.Add (javaTypeName)) {
javaTypes.Add (javaTypeName);
}
}
return javaTypes;
}

HashSet<string> LoadRetainedTypeKeysFromDgml ()
{
var typeKeys = new HashSet<string> (StringComparer.Ordinal);
foreach (var dgmlFile in NativeAotDgmlFiles) {
using var reader = XmlReader.Create (dgmlFile.ItemSpec, new XmlReaderSettings {
DtdProcessing = DtdProcessing.Prohibit,
XmlResolver = null,
});

while (reader.Read ()) {
if (reader.NodeType != XmlNodeType.Element || reader.LocalName != "Node") {
continue;
}

var label = reader.GetAttribute ("Label");
if (label.IsNullOrEmpty () || !label.StartsWith (TypeMetadataPrefix, StringComparison.Ordinal)) {
continue;
}

var assemblyStart = TypeMetadataPrefix.Length;
var assemblyEnd = label.IndexOf (']', assemblyStart);
if (assemblyEnd < 0 || assemblyEnd == label.Length - 1) {
continue;
}

var assemblyName = label.Substring (assemblyStart, assemblyEnd - assemblyStart);
var managedTypeName = label.Substring (assemblyEnd + 1);
typeKeys.Add ($"{managedTypeName}, {assemblyName}");
}
}

return typeKeys;
}
}
Loading