Skip to content

[build] Pass class files directly to D8 and R8 - #12438

Open
simonrozsival wants to merge 5 commits into
mainfrom
dev/simonrozsival/avoid-classes-zip
Open

[build] Pass class files directly to D8 and R8#12438
simonrozsival wants to merge 5 commits into
mainfrom
dev/simonrozsival/avoid-classes-zip

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Aug 19, 2026

Copy link
Copy Markdown
Member

Related: #10958

Summary

  • stop creating obj/.../android/bin/classes.zip after javac for app builds
  • pass generated .class files directly to class-parse, javac, and D8/R8
  • remove archive creation from the Javac task
  • retain JAR generation only for library/AAR output and legacy jar2xml
  • use the Java compile stamp and loose binding classes as dex target incremental inputs

D8 and R8 both accept individual .class files as program inputs. The Windows command-line limit was originally reported as Xamarin Bugzilla #59651. PR #888 initially addressed it by passing the class directory to dx.jar; PR #911 then introduced classes.zip because j8.jar also needed to consume the classes without exceeding the command-line limit. D8/R8 response files make that workaround unnecessary.

Application projects containing bound AndroidJavaSource files also avoid an intermediate binding JAR: class-parse consumes the loose binding classes directly, the regular Java compilation uses their directory as a classpath entry, and D8/R8 receives the class files as program inputs. Library projects still create a JAR because it is packaged into their AAR; legacy jar2xml also continues to require a JAR.

Performance

Balanced 5+5 clean MAUI Debug Android builds using --no-restore:

Typemap ZIP-based mean ZIP-free mean Improvement
LLVM-IR 47.448s 43.630s 3.818s / 8.0%
Trimmable 50.846s 46.736s 4.110s / 8.1%

The Javac task mean fell from 4.235s to 2.757s for LLVM-IR and from 6.890s to 3.278s for trimmable because it no longer packages the class output.

The final loose binding-class implementation was also compared directly with the earlier implementation that still created a binding JAR in application projects:

Bound Java app, clean Debug Binding JAR Loose classes Improvement
LLVM-IR, 10 runs each 10.791s 10.597s 0.194s / 1.8%
Trimmable, 5 runs each 12.620s 12.164s 0.456s / 3.6%
No-op incremental, LLVM-IR, 10 runs each 2.022s 1.696s 0.326s / 16.1%

Binding Javac time was unchanged, while passing the class file directly to class-parse was 106–120ms faster on average. No application build in the loose-class sample produced a binding JAR.

Validation

  • focused D8Tests: 2 passed
  • clean Debug D8 MAUI build
  • incremental Debug D8 MAUI build
  • clean Release R8 MAUI build
  • bound Java application builds with D8 and R8
  • binding library project consumed by an application
  • verified D8 and R8 builds produce signed APKs without classes.zip or an application binding JAR

Avoid creating an intermediate classes.zip after javac. D8 and R8 accept individual .class inputs through their response files, eliminating redundant archive I/O.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings August 19, 2026 12:32

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

This PR updates the Android build pipeline to avoid generating an intermediate classes.zip for application builds, instead passing compiled .class files directly into D8/R8 via the existing response-file mechanism. This aligns the build tasks with D8/R8 supported inputs and reduces unnecessary I/O during javac.

Changes:

  • Stop producing obj/.../android/bin/classes.zip for app builds; use .class files from $(_AndroidIntermediateJavaClassDirectory) as D8/R8 program inputs.
  • Update _CompileToDalvik incremental inputs to use the Java compile stamp ($(_AndroidCompileJavaStampFile)) instead of classes.zip.
  • Adjust D8 task API/response-file generation and update D8 unit tests accordingly.
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Xamarin.Android.D8.targets Passes @(_JavaClassFile) (compiled .class files) directly to D8/R8 instead of classes.zip.
src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets Removes the classes.zip intermediate property and switches dex incremental input to the Java compile stamp.
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/D8Tests.cs Extends the D8 response-file unit test to cover .class inputs.
src/Xamarin.Android.Build.Tasks/Tasks/D8.cs Replaces ClassesZip with ClassFiles and emits .class paths as program inputs in the response file.
src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Javac.targets Stops deleting/passing classes.zip for app Java compilation, leaving binding JAR generation intact elsewhere.

Review details

  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Lite

Update BuildIncrementingClassName to validate the class output directory now that app builds no longer create classes.zip.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Aug 19, 2026
Outputs="$(_AndroidCompileJavaStampFile)">

<!-- remove existing <Javac /> outputs, since *.class files and classes.zip could contain old files -->
<!-- remove existing <Javac /> outputs, since *.class files could contain old files -->

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The <Javac/> task still has the ClassesZip property and code to create the classes.zip, can we remove that, too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 3d7d113. Javac no longer has a ClassesZip property or archive-writing code. _CompileBindingJava now creates its required binding JAR separately with MSBuild’s ZipDirectory task.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Follow-up in ceb8a43: class-parse app builds now avoid the binding JAR as well. Their bound .class files flow directly to class-parse, javac, and D8/R8. JAR creation remains only for library/AAR output and legacy jar2xml.

simonrozsival and others added 2 commits August 20, 2026 14:17
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pass bound Java class files directly to class-parse, javac, and D8/R8.
Retain JAR creation for library packaging and legacy jar2xml builds.

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

Copy link
Copy Markdown
Member

/review

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12438

@github-actions github-actions Bot 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.

⚠️ Needs Changes — 0 errors, 1 warning, 0 suggestions.

The loose .class flow is coherently wired through javac, class-parse, and D8/R8, and retaining the JAR for binding projects and jar2xml preserves the required archive consumers. The added app-build assertion must first use an actual bound Java source; as written, it cannot catch regressions in the new _JavaBindingClassFile path.

CI build 1560618 is currently red and still in progress: both macOS package-test APK lanes are failing while several Windows, Linux, and emulator lanes remain active. I could not verify that those failures are related to this change, so this review does not attribute them to the PR.

Generated by Android PR Reviewer for #12438 · gpt56 · 400.8 AIC · ⌖ 8.95 AIC · ⊞ 25.7K
Comment /review to run again

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

@jonathanpeppers jonathanpeppers left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I reran to see if that would help, but I think this is broken on APK tests:

EXEC : warning : [options] system modules path not set in conjunction with -source 17 [/Users/runner/work/1/s/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.NET.csproj]
/Users/runner/work/1/s/bin/Release/dotnet/packs/Microsoft.Android.Sdk.Darwin/37.0.0-ci.pr.gh12438.0/tools/Xamarin.Android.Bindings.ClassParse.targets(51,5): error MSB6006: "dotnet" exited with code 1. [/Users/runner/work/1/s/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj]
/Users/runner/work/1/s/bin/Release/dotnet/packs/Microsoft.Android.Sdk.Darwin/37.0.0-ci.pr.gh12438.0/tools/Xamarin.Android.Bindings.ClassParse.targets(51,5): error XACLP0000: class-parse: Unable to read file 'obj/Debug/net11.0-android/binding/bin/classes/net/dot/android/test/Example%241.class': Could not find file '/Users/runner/work/1/s/tests/Mono.Android-Tests/Mono.Android-Tests/obj/Debug/net11.0-android/binding/bin/classes/net/dot/android/test/Example%241.class'. [/Users/runner/work/1/s/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj]
/Users/runner/work/1/s/bin/Release/dotnet/packs/Microsoft.Android.Sdk.Darwin/37.0.0-ci.pr.gh12438.0/tools/Xamarin.Android.Bindings.ClassParse.targets(51,5): error XACLP0000: class-parse: Unable to read file 'obj/Debug/net11.0-android/binding/bin/classes/net/dot/android/test/InterfaceMarshalling%241.class': Could not find file '/Users/runner/work/1/s/tests/Mono.Android-Tests/Mono.Android-Tests/obj/Debug/net11.0-android/binding/bin/classes/net/dot/android/test/InterfaceMarshalling%241.class'. [/Users/runner/work/1/s/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj]
Build failed with exit code: 1.

@jonathanpeppers

jonathanpeppers commented Aug 20, 2026

Copy link
Copy Markdown
Member

If we kept <Javac/> writing the classes.zip in some cases for now and just didn't pass it to d8/r8 -- it would be ok to drop the most recent changes.

I didn't realize it complicated some of the binding code paths.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants