Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
0224946
Compare trimmable typemap APK contents
simonrozsival May 14, 2026
7e50c16
Enable R8 shrinking for trimmable typemap comparison
simonrozsival May 14, 2026
f09040b
Pass per-assembly typemap entries to ILLink
simonrozsival May 14, 2026
dcb3410
Refine trimmable typemap framework roots
simonrozsival May 14, 2026
8d8de69
Support DynamicCodeSupport=false with trimmable typemaps
simonrozsival May 20, 2026
87109ad
Keep trimming PR focused on product improvements
simonrozsival May 26, 2026
c6b6c00
Fix post-trim trimmable typemap R8 inputs
simonrozsival May 20, 2026
40204ef
Use coded errors for trimmable typemap task
simonrozsival May 20, 2026
f218cfa
Use post-trim Java sources for trimmable typemap stubs
simonrozsival May 26, 2026
18d6f0f
Compare trimmable typemap APK contents
simonrozsival May 14, 2026
98223cb
Enable R8 shrinking for trimmable typemap comparison
simonrozsival May 14, 2026
91d61f7
Pass per-assembly typemap entries to ILLink
simonrozsival May 14, 2026
657165f
Refine trimmable typemap framework roots
simonrozsival May 14, 2026
9ec7a22
Support DynamicCodeSupport=false with trimmable typemaps
simonrozsival May 20, 2026
253a264
Keep trimming PR focused on product improvements
simonrozsival May 26, 2026
70660e3
Address PR review feedback for trimmable typemap
Copilot May 26, 2026
2c25481
Fix trimmable typemap startup roots
simonrozsival May 26, 2026
5ada554
Merge dev/simonrozsival/trimmable-framework-roots into trimmable-sing…
Copilot May 28, 2026
c88f4fc
Merge main into trimmable single-RID branch
Copilot May 29, 2026
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
2 changes: 2 additions & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4250](xa4250.md): Manifest-referenced type '{type}' was not found in any scanned assembly. It may be a framework type.
+ [XA4252](xa4252.md): Insecure HTTP Maven repository URL '{url}' is not allowed. Use an HTTPS URL, or set AllowInsecureHttp="true" metadata on the item to override this check.
+ [XA4253](xa4253.md): Generated Java callable wrapper code changed: '{path}'
+ [XA4254](xa4254.md): Trimmable type map Java source input directory '{input}' and output directory '{output}' must be different.
+ [XA4255](xa4255.md): Generated trimmable type map Java source '{path}' was not found.
+ XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI.
+ [XA4301](xa4301.md): Apk already contains the item `xxx`.
+ [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex}
Expand Down
2 changes: 1 addition & 1 deletion Documentation/docs-mobile/messages/xa4253.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ f1_keywords:

## Example messages

```
```text
error XA4253: Generated Java callable wrapper code changed: 'obj/Release/android/src/mono/MonoRuntimeProvider.java'
```

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

# .NET for Android error XA4254

## Example message

```text
error XA4254: Trimmable type map Java source input directory 'obj/Release/net11.0-android/typemap/java' and output directory 'obj/Release/net11.0-android/typemap/java' must be different.
```

## Issue

The trimmable type map build tried to clean the Java source output directory, but the configured input and output directories resolved to the same path.

Cleaning the output directory in this configuration would delete the input Java sources before they can be copied.

## Solution

This error indicates an internal build configuration problem. File an issue at <https://github.com/dotnet/android/issues> and include the full build log.
27 changes: 27 additions & 0 deletions Documentation/docs-mobile/messages/xa4255.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: .NET for Android error XA4255
description: XA4255 error code
ms.date: 05/20/2026
f1_keywords:
- "XA4255"
---

# .NET for Android error XA4255

## Example message

```text
error XA4255: Generated trimmable type map Java source 'obj/Release/net11.0-android/typemap/java/my/app/MainActivity.java' was not found.
```

## Issue

The post-trim trimmable type map scan expected to copy a generated Java source file from the pre-trim Java source directory, but the file was missing.

This can happen if intermediate build outputs are stale or if the generated Java source list no longer matches the files on disk.

## Solution

Delete the project's `obj` and `bin` directories, then rebuild.

If the error persists after a clean rebuild, file an issue at <https://github.com/dotnet/android/issues> and include the full build log.
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public sealed record JavaPeerInfo
/// Framework ACWs are generated by the SDK and can be trimmed like bindings unless
/// another rule explicitly roots them.
/// </summary>
public bool IsFrameworkAssembly { get; init; }
public bool IsFrameworkAssembly { get; set; }

/// <summary>
/// True when per-rank array typemap entries should be generated for this peer.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ public TrimmableTypeMapResult Execute (
ManifestConfig? manifestConfig = null,
XDocument? manifestTemplate = null,
string? packageNamingPolicy = null,
int maxArrayRank = 0)
int maxArrayRank = 0,
bool generateTypeMapAssemblies = true)
{
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
Expand All @@ -48,16 +49,15 @@ public TrimmableTypeMapResult Execute (
logger.LogNoJavaPeerTypesFound ();
return new TrimmableTypeMapResult ([], [], allPeers);
}
MarkFrameworkAssemblyPeers (allPeers, frameworkAssemblyNames);

RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));
PropagateDeferredRegistrationToBaseClasses (allPeers);
PropagateCannotRegisterToDescendants (allPeers);

var generatedAssemblies = GenerateTypeMapAssemblies (
allPeers,
systemRuntimeVersion,
useSharedTypemapUniverse,
maxArrayRank);
var generatedAssemblies = generateTypeMapAssemblies
? GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion, useSharedTypemapUniverse, maxArrayRank)
: [];
var jcwPeers = allPeers.Where (ShouldGenerateJcw).ToList ();
logger.LogGeneratingJcwFilesInfo (jcwPeers.Count, allPeers.Count);
var generatedJavaSources = GenerateJcwJavaSources (jcwPeers);
Expand Down Expand Up @@ -212,6 +212,15 @@ List<GeneratedAssembly> GenerateTypeMapAssemblies (
return generatedAssemblies;
}

static void MarkFrameworkAssemblyPeers (List<JavaPeerInfo> allPeers, HashSet<string> frameworkAssemblyNames)
{
foreach (var peer in allPeers) {
if (frameworkAssemblyNames.Contains (peer.AssemblyName)) {
peer.IsFrameworkAssembly = true;
}
}
}

/// <summary>
/// Groups peers by assembly, merging cross-assembly aliases into a single group.
/// When the same JNI name appears in multiple assemblies (e.g. <c>Java.Lang.Object</c>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,45 @@
OutputFile="$(_ProguardProjectConfiguration)" />
</Target>

<Target Name="_GeneratePostTrimTrimmableTypeMapJavaSources"
Condition=" '$(_AndroidTypeMapImplementation)' == 'trimmable' and '$(PublishTrimmed)' == 'true' and Exists('$(IntermediateOutputPath)linked/Link.semaphore') "
AfterTargets="ILLink"
BeforeTargets="_GenerateJavaStubs;_CompileJava;_CompileToDalvik"
Inputs="$(IntermediateOutputPath)linked/Link.semaphore"
Outputs="$(_PostTrimTrimmableTypeMapJavaStamp)">
<ItemGroup>
<_PostTrimTrimmableTypeMapInputAssemblies Include="@(ResolvedFileToPublish)"
Condition=" '%(Extension)' == '.dll' " />
</ItemGroup>

<GenerateTrimmableTypeMap
ResolvedAssemblies="@(_PostTrimTrimmableTypeMapInputAssemblies)"
FrameworkAssemblyNames="@(ResolvedFrameworkAssemblies->'%(Filename)')"
OutputDirectory="$(_TypeMapOutputDirectory)"
JavaSourceOutputDirectory="$(_PostTrimTypeMapJavaOutputDirectory)"
JavaSourceInputDirectory="$(_TypeMapJavaOutputDirectory)"
TargetFrameworkVersion="$(TargetFrameworkVersion)"
PackageNamingPolicy="$(_TrimmableTypeMapPackageNamingPolicy)"
MaxArrayRank="$(_AndroidTrimmableTypeMapMaxArrayRank)"
GenerateTypeMapAssemblies="false"
CleanJavaSourceOutputDirectory="true"
AcwMapOutputFile="$(IntermediateOutputPath)acw-map.txt"
ApplicationRegistrationOutputFile="$(IntermediateOutputPath)android/src/net/dot/android/ApplicationRegistration.java">
<Output TaskParameter="GeneratedJavaFiles" ItemName="_PostTrimGeneratedJavaFiles" />
</GenerateTrimmableTypeMap>

<MakeDir Directories="$([System.IO.Path]::GetDirectoryName('$(_PostTrimTrimmableTypeMapJavaStamp)'))" />
<Touch Files="$(_PostTrimTrimmableTypeMapJavaStamp)" AlwaysCreate="true" />

<ItemGroup>
<FileWrites Include="@(_PostTrimGeneratedJavaFiles)" />
<FileWrites Include="$(IntermediateOutputPath)acw-map.txt" />
<FileWrites Include="$(IntermediateOutputPath)android/src/net/dot/android/ApplicationRegistration.java" />
<FileWrites Include="$(_PostTrimTrimmableTypeMapJavaStamp)" />
<_PostTrimTrimmableTypeMapInputAssemblies Remove="@(_PostTrimTrimmableTypeMapInputAssemblies)" />
<_PostTrimGeneratedJavaFiles Remove="@(_PostTrimGeneratedJavaFiles)" />
</ItemGroup>
</Target>
<!-- Add linked TypeMap DLLs to the normal publish assembly pipeline. The SDK R2R
target only compiles ResolvedFileToPublish items with PostprocessAssembly=true. -->
<Target Name="_AddTrimmableTypeMapToResolvedFileToPublish"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
<_TypeMapOutputDirectory>$(_TypeMapBaseOutputDir)typemap/</_TypeMapOutputDirectory>
<_TypeMapJavaOutputDirectory>$(_TypeMapBaseOutputDir)typemap/java</_TypeMapJavaOutputDirectory>
<_TypeMapAssembliesListFile>$(_TypeMapOutputDirectory)typemap-assemblies.txt</_TypeMapAssembliesListFile>
<_PostTrimTypeMapJavaOutputDirectory>$(_TypeMapBaseOutputDir)typemap/linked-java</_PostTrimTypeMapJavaOutputDirectory>
<_TypeMapJavaStubsSourceDirectory Condition=" '$(_TypeMapJavaStubsSourceDirectory)' == '' and '$(_AndroidRuntime)' == 'CoreCLR' and '$(PublishTrimmed)' == 'true' ">$(_PostTrimTypeMapJavaOutputDirectory)</_TypeMapJavaStubsSourceDirectory>
<_TypeMapJavaStubsSourceDirectory Condition=" '$(_TypeMapJavaStubsSourceDirectory)' == '' ">$(_TypeMapJavaOutputDirectory)</_TypeMapJavaStubsSourceDirectory>
<_PostTrimTrimmableTypeMapJavaStamp>$(_TypeMapBaseOutputDir)stamp/_GeneratePostTrimTrimmableTypeMapJavaSources.stamp</_PostTrimTrimmableTypeMapJavaStamp>
<_TrimmableJavaSourceStamp Condition=" '$(_TrimmableJavaSourceStamp)' == '' and '$(_AndroidRuntime)' == 'CoreCLR' and '$(PublishTrimmed)' == 'true' ">$(_PostTrimTrimmableTypeMapJavaStamp)</_TrimmableJavaSourceStamp>
<_TrimmableJavaSourceStamp Condition=" '$(_TrimmableJavaSourceStamp)' == '' ">$(_TypeMapOutputDirectory)$(_TypeMapAssemblyName).dll</_TrimmableJavaSourceStamp>
Comment on lines +28 to +32
<!-- Max array rank for __ArrayMapRank{N} sentinel emission. Defaults to 3 when
dynamic code is unavailable, so array creation uses the typemap path;
defaults to 0 otherwise, where dynamic code can use Array.CreateInstance directly. -->
Expand Down Expand Up @@ -251,7 +257,7 @@
Outputs="$(_AndroidStampDirectory)_GenerateJavaStubs.stamp">

<ItemGroup>
<_TypeMapJavaFiles Include="$(_TypeMapJavaOutputDirectory)/**/*.java" />
<_TypeMapJavaFiles Include="$(_TypeMapJavaStubsSourceDirectory)/**/*.java" />
</ItemGroup>
<Copy SourceFiles="@(_TypeMapJavaFiles)" DestinationFolder="$(IntermediateOutputPath)android/src/%(RecursiveDir)" />

Expand Down

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

11 changes: 11 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,17 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS
<value>Generated Java callable wrapper code changed: '{0}'</value>
<comment>{0} - The path to the generated Java callable wrapper file</comment>
</data>
<data name="XA4254" xml:space="preserve">
<value>Trimmable type map Java source input directory '{0}' and output directory '{1}' must be different.</value>
<comment>The following are literal names and should not be translated: Trimmable type map, Java.
{0} - Full path to the Java source input directory
{1} - Full path to the Java source output directory</comment>
</data>
<data name="XA4255" xml:space="preserve">
<value>Generated trimmable type map Java source '{0}' was not found.</value>
<comment>The following are literal names and should not be translated: trimmable type map, Java.
{0} - Full path to the generated Java source file</comment>
</data>
<data name="XA0142" xml:space="preserve">
<value>Command '{0}' failed.\n{1}</value>
<comment>'{0}' is a failed command name (potentially with path) followed by all the arguments passed to it. {1} is the combined output on the standard error and standard output streams.</comment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN
public string OutputDirectory { get; set; } = "";
[Required]
public string JavaSourceOutputDirectory { get; set; } = "";
public string? JavaSourceInputDirectory { get; set; }
[Required]
public string TargetFrameworkVersion { get; set; } = "";

Expand Down Expand Up @@ -92,6 +93,8 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN
public string? ManifestPlaceholders { get; set; }
public string? CheckedBuild { get; set; }
public string? ApplicationJavaClass { get; set; }
public bool GenerateTypeMapAssemblies { get; set; } = true;
public bool CleanJavaSourceOutputDirectory { get; set; }

[Output]
public ITaskItem [] GeneratedAssemblies { get; set; } = [];
Expand All @@ -116,8 +119,19 @@ public override bool RunTask ()
foreach (var assemblyName in FrameworkAssemblyNames) {
frameworkAssemblyNames.Add (assemblyName);
}
if (CleanJavaSourceOutputDirectory && !JavaSourceInputDirectory.IsNullOrEmpty ()) {
var inputDirectory = Path.GetFullPath (JavaSourceInputDirectory);
var outputDirectory = Path.GetFullPath (JavaSourceOutputDirectory);
if (string.Equals (inputDirectory, outputDirectory, StringComparison.OrdinalIgnoreCase)) {
Log.LogCodedError ("XA4254", Properties.Resources.XA4254, inputDirectory, outputDirectory);
return false;
}
}

Directory.CreateDirectory (OutputDirectory);
if (CleanJavaSourceOutputDirectory && Directory.Exists (JavaSourceOutputDirectory)) {
Directory.Delete (JavaSourceOutputDirectory, recursive: true);
}
Directory.CreateDirectory (JavaSourceOutputDirectory);

var peReaders = new List<PEReader> ();
Expand Down Expand Up @@ -168,11 +182,16 @@ public override bool RunTask ()
manifestConfig: manifestConfig,
manifestTemplate: manifestTemplate,
packageNamingPolicy: PackageNamingPolicy,
maxArrayRank: MaxArrayRank);
maxArrayRank: MaxArrayRank,
generateTypeMapAssemblies: GenerateTypeMapAssemblies);

GeneratedAssemblies = WriteAssembliesToDisk (result.GeneratedAssemblies, assemblyInputs.Select (i => i.Path).ToList ());
WriteGeneratedAssembliesListFile (GeneratedAssemblies);
GeneratedJavaFiles = WriteJavaSourcesToDisk (result.GeneratedJavaSources);
if (GenerateTypeMapAssemblies) {
GeneratedAssemblies = WriteAssembliesToDisk (result.GeneratedAssemblies, assemblyInputs.Select (i => i.Path).ToList ());
WriteGeneratedAssembliesListFile (GeneratedAssemblies);
}
GeneratedJavaFiles = JavaSourceInputDirectory.IsNullOrEmpty ()
? WriteJavaSourcesToDisk (result.GeneratedJavaSources)
: CopyJavaSourcesFromInputDirectory (result.GeneratedJavaSources);

// Write manifest to disk if generated
if (result.Manifest is not null && !MergedAndroidManifestOutput.IsNullOrEmpty ()) {
Expand Down Expand Up @@ -247,6 +266,29 @@ void WriteGeneratedAssembliesListFile (IReadOnlyList<ITaskItem> assemblies)
Files.CopyIfStringChanged (text, GeneratedAssembliesListFile);
}

ITaskItem [] CopyJavaSourcesFromInputDirectory (IReadOnlyList<GeneratedJavaSource> javaSources)
{
var items = new List<ITaskItem> ();
foreach (var source in javaSources) {
string inputPath = Path.Combine (JavaSourceInputDirectory ?? "", source.RelativePath);
if (!File.Exists (inputPath)) {
Log.LogCodedError ("XA4255", Properties.Resources.XA4255, inputPath);
continue;
}

string outputPath = Path.Combine (JavaSourceOutputDirectory, source.RelativePath);
string? dir = Path.GetDirectoryName (outputPath);
if (!string.IsNullOrEmpty (dir)) {
Directory.CreateDirectory (dir);
}
using (var stream = File.OpenRead (inputPath)) {
Files.CopyIfStreamChanged (stream, outputPath);
}
items.Add (new TaskItem (outputPath));
}
return items.ToArray ();
}

ITaskItem [] WriteAssembliesToDisk (IReadOnlyList<GeneratedAssembly> assemblies, IReadOnlyList<string> assemblyPaths)
{
// Build a map from assembly name -> source path for timestamp comparison
Expand Down
Loading