Skip to content

[TrimmableTypeMap] Generate the typemap before the NativeAOT inner build so the IDE Compile→Sign flow works#11781

Open
simonrozsival wants to merge 2 commits into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/fix-nativeaot-typemap-ide-sign
Open

[TrimmableTypeMap] Generate the typemap before the NativeAOT inner build so the IDE Compile→Sign flow works#11781
simonrozsival wants to merge 2 commits into
dotnet:mainfrom
simonrozsival:dev/simonrozsival/fix-nativeaot-typemap-ide-sign

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Summary

The NativeAOT trimmable typemap (the generated Java ↔ .NET type map) is consumed by the inner per-RID ILC build — via _AddTrimmableTypeMapAssembliesToIlc_ReadGeneratedTrimmableTypeMapAssemblies — which reads the generated assembly list at obj/.../typemap/typemap-assemblies.txt. The inner build cannot generate that list: _GenerateTrimmableTypeMap is gated to the outer build ('$(_OuterIntermediateOutputPath)' == '').

_GenerateTrimmableTypeMap's only trigger is AfterTargets="CoreCompile". That hook does not fire when compilation is already up-to-date — which is exactly what happens in the IDE's split build, where Visual Studio (BuildingInsideVisualStudio=true) runs Compile and SignAndroidPackage as two separate MSBuild invocations. During the SignAndroidPackage call the C# is already compiled, so CoreCompile is skipped, the typemap is never generated, and the inner ILC build fails with:

Trimmable typemap assembly list '.../typemap/typemap-assemblies.txt' was not found

A regular command-line Build works because all targets run in one continuous invocation; only the IDE Compile→Sign sequence hits this. Reproduced by the DesignTimeBuildSignAndroidPackage(NativeAOT) test.

Fixes #11775.

Fix

Add a target that forces the typemap to be generated in the outer build before _ResolveAssemblies spawns the inner per-RID ILC build:

<Target Name="_EnsureTrimmableTypeMapGeneratedForNativeAotInnerBuild"
    Condition=" '$(RuntimeIdentifier)' == '' "
    BeforeTargets="_ResolveAssemblies"
    DependsOnTargets="_GenerateTrimmableTypeMap" />

Instead of relying on the fragile AfterTargets="CoreCompile" hook, generation is anchored to _ResolveAssemblies, which always runs before the inner build and runs after compilation (it consumes @(IntermediateAssembly)), so the generator still observes the compiled app assembly. This mirrors the CoreCLR _AddTrimmableTypeMapToLinker target, which forces generation before the (outer) ILLink.

Risk

Low and well-scoped: the new target is conditioned to the outer build (RuntimeIdentifier == '') and lives in the NativeAOT trimmable targets file; it only ensures an already-required step has run. No effect on CoreCLR, managed, or MonoVM paths.

Testing

Verified locally: DesignTimeBuildSignAndroidPackage(NativeAOT) now passes (was failing); DesignTimeBuildSignAndroidPackage(CoreCLR) continues to pass.

Sliced out of #11617 for focused review.

The NativeAOT trimmable typemap is consumed only in the inner per-RID ILC build
(via _AddTrimmableTypeMapAssembliesToIlc -> _ReadGeneratedTrimmableTypeMapAssemblies),
which cannot generate it: _GenerateTrimmableTypeMap is gated to the outer build
(_OuterIntermediateOutputPath == ''). Its only trigger is AfterTargets="CoreCompile",
which does not fire when compilation is up-to-date - e.g. the IDE's separate
"Compile" then "SignAndroidPackage" invocations (BuildingInsideVisualStudio=true).
The outer build therefore never generated obj/.../typemap/typemap-assemblies.txt and
the inner build failed with "Trimmable typemap assembly list ... was not found".

Force _GenerateTrimmableTypeMap to run in the outer build before _ResolveAssemblies
spawns the inner per-RID build. _ResolveAssemblies runs after compilation (it consumes
@(IntermediateAssembly)), so the generator still observes the compiled app assembly.
Mirrors the CoreCLR _AddTrimmableTypeMapToLinker target, which forces generation before
the (outer) ILLink.

Verified locally: DesignTimeBuildSignAndroidPackage(NativeAOT) now passes (was failing);
DesignTimeBuildSignAndroidPackage(CoreCLR) continues to pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 28, 2026 14:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ensures the NativeAOT trimmable typemap is generated early enough in the outer build so that IDE “Compile → SignAndroidPackage” (separate MSBuild invocations) doesn’t fail when CoreCompile is skipped and the typemap assembly list hasn’t been produced yet.

Changes:

  • Adds a NativeAOT-specific target to force _GenerateTrimmableTypeMap to run before _ResolveAssemblies triggers inner per-RID work.
  • Documents why the new ordering is needed (IDE split build vs single-invocation CLI build).
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.NativeAOT.targets Adds a pre-_ResolveAssemblies “ensure typemap generated” target for NativeAOT builds.

Copilot's findings

  • Files reviewed: 1/1 changed files
  • Comments generated: 2

…tifier

The "outer build" is the build that is not an inner per-RID build, which is
determined by '$(_OuterIntermediateOutputPath)' == '' — the same discriminator
_GenerateTrimmableTypeMap itself uses. The previous 'RuntimeIdentifier == ''
condition missed the single-RID outer build (e.g. -p:RuntimeIdentifier=android-arm64),
where RuntimeIdentifier is non-empty but the build is still the outer one, leaving
the typemap ungenerated and reproducing the same failure this change fixes.

The inner build receives _OuterIntermediateOutputPath via _ResolveAssemblies
AdditionalProperties, so the target is still correctly skipped there.

Verified locally: DesignTimeBuildSignAndroidPackage(NativeAOT) and (CoreCLR) both pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

Good catch — fixed in e68ee2c. The target now gates on '$(_OuterIntermediateOutputPath)' == '' (the same discriminator _GenerateTrimmableTypeMap itself uses) instead of RuntimeIdentifier == '', so single-RID outer builds (e.g. -p:RuntimeIdentifier=android-arm64) are covered too. The inner build sets _OuterIntermediateOutputPath via _ResolveAssemblies AdditionalProperties, so the target is still correctly skipped there. Verified locally: DesignTimeBuildSignAndroidPackage(NativeAOT) and (CoreCLR) both pass.

@simonrozsival simonrozsival added copilot `copilot-cli` or other AIs were used to author this trimmable-type-map labels Jun 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TrimmableTypeMap] IDE Compile→SignAndroidPackage flow doesn't generate the typemap (DesignTimeBuildSignAndroidPackage)

2 participants