Skip to content

[Build] Split _GenerateJavaStubs into independent sub-targets for incremental build performance#11056

Draft
davidnguyen-tech wants to merge 6 commits intodotnet:mainfrom
davidnguyen-tech:feature/split-generate-java-stubs
Draft

[Build] Split _GenerateJavaStubs into independent sub-targets for incremental build performance#11056
davidnguyen-tech wants to merge 6 commits intodotnet:mainfrom
davidnguyen-tech:feature/split-generate-java-stubs

Conversation

@davidnguyen-tech
Copy link
Copy Markdown
Member

Summary

Split the monolithic _GenerateJavaStubs target into four independent sub-targets, each with their own Inputs/Outputs for MSBuild incrementality:

  • _GenerateJavaCallableWrappers: JCW generation + ACW map (reads .jlo.xml sidecar files)
  • _GenerateJavaStubsCore: Code gen state production + marshal method rewriting
  • _GenerateTypeMappings: Type map .ll file generation (highest-value split — 2.21s on CoreCLR)
  • _GenerateAndroidManifest: Manifest + provider source generation

The original _GenerateJavaStubs target is preserved as a no-op wrapper with DependsOnTargets for backward compatibility.

Motivation

In a MAUI Android CoreCLR incremental build (13.6s total), _GenerateJavaStubs takes ~3.24s and is the single largest bottleneck. It contains 8 sequential tasks gated by a single Inputs/Outputs pair. When the assembly changes (any C# edit), all 8 tasks execute even if only one has work to do.

This triggers a cascade: GenerateTypeMappings regenerates .ll files → _CompileNativeAssemblySources recompiles them (2.85s). Total cascade cost: ~5.5s of unnecessary work.

Changes

Per-file up-to-date check (CompileNativeAssembly.cs)

Added timestamp check so unchanged .ll → .o compilations are skipped, even when the target runs.

C# task changes (GenerateTypeMappings.cs, GenerateNativeApplicationConfigSources.cs)

Externalized NativeCodeGenState.TemplateJniAddNativeMethodRegistrationAttributePresent as an MSBuild [Output] property, persisted to a cache file. This decouples cross-target static state for incremental scenarios.

MSBuild target changes

  • Xamarin.Android.Common.targets: Added 4 input gatherer sub-targets with narrowed inputs, wired JNI registration cache to downstream target
  • Microsoft.Android.Sdk.TypeMap.LlvmIr.targets: Split monolithic target into 4 sub-targets with independent stamp files
  • Microsoft.Android.Sdk.TypeMap.Trimmable.targets: Added matching stub sub-targets

Tests (IncrementalBuildTest.cs, AndroidUpdateResourcesTest.cs)

  • Updated existing GenerateJavaStubsAndAssembly test to cover sub-targets
  • Added GenerateJavaStubsSubTargetIncrementality test verifying sub-targets skip independently
  • Updated skip assertions to use _GenerateJavaStubsCore instead of wrapper target

davidnguyen-tech and others added 6 commits March 30, 2026 19:19
When _CompileNativeAssemblySources runs, it recompiles ALL .ll files
even if only some have changed. Add a per-file timestamp check in
RunAssembler() so that if the output .o is newer than the input .ll,
that file is skipped. This complements the upcoming target split by
ensuring unchanged type map files are not recompiled.

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

Add [Output] JniAddNativeMethodRegistrationAttributePresent to
GenerateTypeMappings and a matching input property on
GenerateNativeApplicationConfigSources. This decouples the cross-target
static state so the value can be persisted to a cache file and read
back when _GenerateTypeMappings is skipped on incremental builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add four input gatherer targets that compute narrowed input sets for
the upcoming sub-targets of _GenerateJavaStubs:

- _GetGenerateJavaCallableWrappersInputs: .jlo.xml sidecar files
- _GetGenerateJavaStubsCoreInputs: assembly hash, manifest, environment
- _GetGenerateTypeMappingsInputs: .typemap.xml sidecar files
- _GetGenerateAndroidManifestInputs: manifest template, environment

Each sub-target has narrower inputs than the monolithic target, enabling
MSBuild to skip unchanged sub-tasks on incremental builds. The core
inputs include manifest and environment files to ensure NativeCodeGenState
is always registered when downstream targets need it.

Also wire the JNI registration attribute cache file to
GenerateNativeApplicationConfigSources via ReadLinesFromFile.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split the monolithic _GenerateJavaStubs target into four independent
sub-targets, each with their own Inputs/Outputs for incrementality:

- _GenerateJavaCallableWrappers: JCW generation + ACW map
- _GenerateJavaStubsCore: code gen state + marshal method rewriting
- _GenerateTypeMappings: type map .ll file generation
- _GenerateAndroidManifest: manifest + provider source generation

The original _GenerateJavaStubs target is preserved as a no-op wrapper
with DependsOnTargets for backward compatibility. Sub-targets explicitly
depend on _GetGenerateJavaStubsInputs to ensure @(_EnvironmentFiles)
is populated. The JNI registration attribute flag is persisted to a
cache file for downstream targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add matching stub sub-targets in the Trimmable typemap targets file
to maintain consistency with the LlvmIr path. All sub-targets are
stubs that skip immediately, matching the existing behavior where
_GenerateJavaStubs was a single stub target.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Update the existing GenerateJavaStubsAndAssembly test to verify the
new sub-targets follow the same incrementality rules. Add a new
GenerateJavaStubsSubTargetIncrementality test verifying all sub-targets
skip on no-change rebuilds.

Update skip assertions to check _GenerateJavaStubsCore instead of the
wrapper _GenerateJavaStubs (which has no Inputs/Outputs and never
reports as skipped).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant