镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

[Xamarin.Android.Build.Tasks] Extract .aar files with MSBuild targets - #13510

Closed
simonrozsival wants to merge 5 commits into
mainfrom
simonrozsival-explain-resolve-library-project-imports
Closed

simonrozsival wants to merge 5 commits into
mainfrom
simonrozsival-explain-resolve-library-project-imports

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Context: fcd7cf8 (#5131)

Android libraries can carry Android content (resources, assets, Java libraries, native libraries, ProGuard rules, environment files) in two formats:

  • "Classic" Xamarin.Android class libraries embed it in the assembly as __AndroidLibraryProjects__.zip, __AndroidNativeLibraries__.zip, and *.jar managed resources.
  • Since fcd7cf8, .NET for Android class libraries produce an .aar file next to the assembly instead.

<ResolveLibraryProjectImports/> handled both formats in one ~520 line task. Every time the target ran, it hashed every referenced assembly and .aar. The .aar cache-hit path also duplicated the extraction path and drifted from it: .net/env/ environment files were dropped whenever an .aar was not re-extracted.

Nothing in .NET for Android produces the classic format anymore, so split the two formats.

.aar extraction in MSBuild

The new _AndroidExtractAarLibraries and _AndroidExtractChangedAarLibrary targets replace the C# .aar handling with <GetFileHash/>, <Unzip/>, and per-.aar partial builds:

  • Each .aar is extracted to lp/[id]/jl/, where [id] is a hash of the .aar path. Previously it was lp/[N], with [N] coming from map.cache. As before, .jar files other than classes.jar and libs/**/*.jar are not extracted, and neither are __MACOSX or .DS_Store entries.
  • lp/[id].stamp holds the SHA256 of the .aar and is only rewritten when the content changes. Touching an unchanged .aar rehashes only that file, and no downstream targets run.
  • Changed .aar files are extracted by a target batched per .aar. Each batch deletes that .aar's stamps first and only writes them back once <RemoveDir/>, both <Unzip/> passes and <Touch/> have succeeded. A failed extraction (an error, or a warning in design-time builds) is therefore retried by the next build. Without this, extracting A, failing to extract a corrupt replacement, and then restoring A would match A's stale hash and skip re-extraction.
  • Extracted files are touched after extraction, because .aar entries often carry fixed timestamps (1980).
  • lp/aar.map ([id]=[path] lines) lets JavaToolTask keep mapping lp/[id] paths in javac/d8/r8 errors back to their .aar. It is also an input of _UpdateAndroidResgen and _BuildLibraryImportsCache, so adding or removing an .aar regenerates resources.
  • Resource, asset, environment, ProGuard, and stamp items are derived from the extracted directories on every build. This fixes the dropped .net/env/ files.
  • The content stamp of every .aar is part of @(_LibraryResourceDirectoryStamps). As a result, an update that removes all of an .aar's resources still runs _UpdateAndroidResgen, instead of leaving stale IDs in R.txt.
  • Paths are built with NormalizeDirectory(), so they use native separators. On Windows, mixed separators prevented custom-view resources from matching their recompilation inputs.

Classic assemblies: deprecated, still supported

<ResolveLibraryProjectImports/> now only handles classic assemblies. It stays enabled by default and emits the new warning XA0150 when it finds embedded Android content:

warning XA0150: NuGet package 'Some.Old.Package' version '1.2.3' contains assembly '/path/to/Some.Old.Package.dll' with Android library content embedded in the legacy Xamarin.Android format. Support for this format is deprecated and will be removed in a future version of .NET for Android. …

When an updated assembly no longer contains classic content, its leftover lp/[N] directory and stamp are deleted. Otherwise later builds would keep logging XA0150 for it and resolve stale jars and resources.

This change also removes:

  • the __library_projects__ → lp directory migration;
  • an unreachable __AndroidEnvironment__ cleanup branch;
  • the now-unused task parameters.

Behavior changes

  • A corrupt .aar now fails with MSB3933 from <Unzip/> instead of XA1004. xa1004.md is updated accordingly.
  • The internal_impl-*.jar rename (only seen in old support-library .aar files) is gone.
  • After an .aar is removed, its assets remain in the .apk until _CreateBaseApk runs for another reason. This also happens on main.

Testing

Run on macOS against a locally built SDK (make prepare && make leeroy):

  • A/B comparison: an app referencing Xamarin.AndroidX.AppCompat (52 .aar files) was built with and without this change. Both produced identical @(LibraryResourceDirectories) (same order), assets, ProGuard rules, jars and manifests. The .apk was byte-identical: every entry CRC matched, including resources.arsc.
  • Manual incremental scenarios: with a custom .aar containing res/, assets/, proguard.txt, .net/env/ and lint.jar, I exercised a no-op build, a touch-only change, a content change and removing the .aar. I also ran a design-time build with a corrupt .aar, which logged a warning and wrote no stamps; the next build retried the .aar and failed.
  • New tests:
    • ResolveLibraryProjectImportsTests covers XA0150 on fresh and cached runs, the NuGet and plain-assembly variants, no warning for modern assemblies or for a legacy → modern transition, and the aar.map lookup.
    • IncrementalBuildTest.AarResourcesRemovedRegeneratesResources and IncrementalBuildTest.AarIsExtractedAgainAfterFailedExtraction cover the incremental fixes.
    • Each regression test was confirmed to fail without its fix.
  • CoreCLR AAR, binding and incremental tests: 44/44 passed (./dotnet-local.sh test bin/TestDebug/net11.0/Xamarin.Android.Build.Tests.dll --filter …). This includes AarContentExtraction, DotNetBuildLibrary, BindingCheckHiddenFiles, IncrementalBuildTest.ResolveLibraryProjectImports and AppProjectTargetsDoNotBreak.
  • NativeAOT: the NativeAOT variant of AarContentExtraction passed.
  • Broader AAR-related CoreCLR run (99 tests, before the review fixes): 91 passed, 3 were skipped, and 5 failed for environmental reasons. Four were BuildReleaseArm64 (apkdiff is not installed) and one was BindLibraryWithMultipleGradleVersions (Gradle 8.7 does not run on JDK 23).

Context: classic Xamarin.Android class libraries embedded Android
content in assemblies as `__AndroidLibraryProjects__.zip`,
`__AndroidNativeLibraries__.zip`, and `*.jar` resources. .NET for
Android libraries ship `.aar` files instead, but both formats were
handled by `<ResolveLibraryProjectImports/>`.

Move `.aar` extraction into the new `_AndroidExtractAarLibraries`
target, using `<GetFileHash/>`, `<Unzip/>`, and per-`.aar` partial
builds:

  * Each `.aar` is extracted to `lp/[hash of path]/jl/`, skipping
    `.jar` files other than `classes.jar` and `libs/*.jar`.
  * `lp/[id].stamp` holds the `.aar` SHA256 and is only rewritten when
    the content changes, so touching an unchanged `.aar` no longer
    invalidates downstream targets.
  * `lp/aar.map` maps `lp/[id]` to the `.aar` path for tool error
    messages, and invalidates `_UpdateAndroidResgen` and
    `_BuildLibraryImportsCache` when `.aar` files are added or removed.
  * Items are computed from the extracted directories on every build,
    which also fixes `.net/env/` files being dropped when an `.aar`
    was not re-extracted.

`<ResolveLibraryProjectImports/>` now only handles classic assemblies.
Support for them is deprecated: emit XA0150 when such content is found.
Also remove the `__library_projects__` directory migration and an
unreachable `__AndroidEnvironment__` cleanup branch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 15:37

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.

🟡 Changes recommended

The AAR changes can leave stale resource IDs, mark failed extractions as complete, and pass macOS metadata files to d8/R8 as invalid jars.

3 open issues
1 file not reviewed
  • src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs: Generated file
What changed in this PR

This PR separates modern AAR extraction from legacy Xamarin.Android embedded-resource handling, moving AAR processing into incremental MSBuild targets to reduce unnecessary build work.

Changes:

  • Add per-AAR extraction, content hashes, and path mapping for diagnostics and cache invalidation.
  • Retain legacy extraction while introducing the documented XA0150 deprecation warning.
  • Update tests and helpers for the hashed directory layout and incremental behavior.
File Description
src/​Xamarin.Android.Build.Tasks/​Xamarin.Android.EmbeddedResource.targets Adds incremental AAR extraction and derives library items from extracted content.
src/​Xamarin.Android.Build.Tasks/​Xamarin.Android.Common.targets Adds the AAR map to resource-generation inputs.
src/​Xamarin.Android.Build.Tasks/​Utilities/​AssemblyIdentityMap.cs Resolves hashed AAR directories and corrects index bounds checking.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.ProjectTools/​Common/​BuildOutput.cs Adds an AAR directory lookup helper.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​Tasks/​ResolveLibraryProjectImportsTests.cs Tests legacy warnings, extraction, and AAR map lookup.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​IncrementalBuildTest.cs Updates cache and incremental-build assertions.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​BuildTest.cs Checks AAR extraction and unchanged-content stamp preservation.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​BindingBuildTest.cs Updates binding tests for hashed paths and hidden-file handling.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​AndroidUpdateResourcesTest.cs Uses AAR map lookup in custom-view resource tests.
src/​Xamarin.Android.Build.Tasks/​Tasks/​ResolveLibraryProjectImports.cs Removes AAR processing and warns about legacy embedded content.
src/​Xamarin.Android.Build.Tasks/​Tasks/​JavaToolTask.cs Recognizes hexadecimal import directories in tool diagnostics.
src/​Xamarin.Android.Build.Tasks/​Properties/​Resources.resx Adds XA0150 warning messages.
src/​Xamarin.Android.Build.Tasks/​Properties/​Resources.Designer.cs Adds accessors for the new warning messages.
Documentation/​docs-mobile/​TOC.yml Adds XA0150 to documentation navigation.
Documentation/​docs-mobile/​messages/​xa1004.md Clarifies legacy archive errors versus AAR extraction errors.
Documentation/​docs-mobile/​messages/​xa0150.md Documents legacy-format deprecation and migration guidance.
Documentation/​docs-mobile/​messages/​index.md Adds XA0150 to the warning index.

Review effort: Balanced

simonrozsival and others added 2 commits November 11, 2026 17:37
  * Exclude `__MACOSX` entries from the `.jar` extraction pass, so
    macOS metadata such as `libs/__MACOSX/._foo.jar` is not passed to
    d8/R8.
  * Extract changed `.aar` files in the batched
    `_AndroidExtractChangedAarLibrary` target and only write the
    `[id].stamp` and `extracted.stamp` of an `.aar` when all of its
    steps succeeded. Errors are warnings in design-time builds; a
    failed `.aar` is now extracted again by the next build.
  * Add the content stamp of every `.aar` to
    `@(_LibraryResourceDirectoryStamps)`, so removing all resources
    from an `.aar` still invalidates `_UpdateAndroidResgen`.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use native directory separators consistently for extracted AAR contents. On Windows, mixed separators prevented custom-view resources from matching their recompilation inputs and made consumer ProGuard configuration paths inconsistent.

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

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #13510

@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

2 inline findings: 1 ❌ error, 1 ⚠️ warning, 0 💡 suggestions.

The extraction split is clear, and separating content hashes from completion timestamps supports the intended touch-only optimization. Deriving AAR items on every build also addresses the cached environment/ProGuard paths, and the resource-removal regression test is valuable. The previously reviewed macOS-entry filtering and resource-stamp fixes are present.

The remaining findings concern stale stamps after a failed AAR replacement and a false XA0150 warning after an assembly's legacy content is removed. Details and concrete regression scenarios are attached inline.

CI: All 40 GitHub checks report success, including the aggregate dotnet-android check for Azure build 1678826. Independent Azure build/timeline verification was blocked because az devops invoke required authentication.

Local verification: An isolated reproduction importing the production extraction targets was attempted, but .NET first-run initialization failed before MSBuild executed. The findings are based on source analysis, not a successfully executed reproduction. No repository files were changed.

Generated by Android PR Reviewer for #13510 · copilot · gpt61 · 107.8 AIC · ⌖ 4.9 AIC · ⊞ 19.5K
Comment /review to run again

  * `_AndroidExtractChangedAarLibrary` deletes the `[id].stamp` and
    `extracted.stamp` of an `.aar` before modifying its extraction
    directory. Previously, extracting A, failing to extract a corrupt
    B, then restoring A matched the stale hash of A and skipped
    re-extraction, leaving the directory deleted.
  * `<ResolveLibraryProjectImports/>` deletes the extraction directory
    and stamp of an assembly that no longer contains legacy content,
    instead of logging XA0150 for leftover directories on later builds.

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

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #13510

@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

Findings: 0 errors, 1 warning, 0 suggestions. The new resource-presence checks discard a boolean result rather than asserting it; the single inline comment covers both affected tests. No additional product-code defect was confirmed.

The extraction split is clear, the per-AAR stamp invalidation addresses failed-replacement recovery, and retaining content stamps for resource-free AARs addresses stale resource generation. XA0150 also has localized messages, documentation, and coverage for legacy-to-modern transitions.

CI and validation: All 40 reported GitHub checks succeeded, including the dotnet-android checks for Azure build 1679311. Direct Azure build/timeline verification was blocked by the CLI requiring authentication, so that status was not independently confirmed. An attempted isolated MSBuild check was blocked by execution approval; no local tests ran. Review findings are based on the diff, full changed-file context, and related callers/helpers.

Generated by Android PR Reviewer for #13510 · copilot · gpt61 · 106.5 AIC · ⌖ 3.88 AIC · ⊞ 20.2K
Comment /review to run again

`StringAssertEx.ContainsText()` returns a `bool` and never asserts, so `AarResourcesRemovedRegeneratesResources` and `AarIsExtractedAgainAfterFailedExtraction` did not verify that `R.txt` contained the `.aar` resource. Assert the result.

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

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

✅ Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #13510

@simonrozsival

Copy link
Copy Markdown
Member Author

Looking back, this change does not seem beneficial enough to justify the added complexity. I'm closing this to reduce distractions. We can revisit it later.

@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

Findings: 1 ❌ error, 0 ⚠️ warnings, 1 💡 suggestion, posted inline.

The extraction split is well scoped, and the per-AAR stamp invalidation plus regression tests address failed-extraction recovery and removal of all resources. One incremental correctness gap remains: cleaning up a native-only legacy DLL can leave the downstream imports cache referencing deleted native libraries. The inline finding describes the input sequence and cache-invalidation chain. I also suggested automated coverage for the environment-file cache-hit fix.

CI: All 40 GitHub checks report success, including the aggregate dotnet-android check for Azure build 1679764. Direct Azure build/timeline verification was blocked because az devops invoke requires authentication in this environment. Local reproduction was not completed because execution permissions denied fixture generation; the correctness finding is based on tracing the task outputs, SaveIfChanged, target inputs, and downstream cache consumers.

Generated by Android PR Reviewer for #13510 · copilot · gpt61 · 128.7 AIC · ⌖ 5.02 AIC · ⊞ 21K
Comment /review to run again

// Left over from a previous version of this assembly; keeping it would resolve stale content
// and log XA0150 when the hash matches the stamp on the next build.
Log.LogDebugMessage ($"Deleting '{outDirForDll}': '{assemblyPath}' no longer contains legacy Android library content");
Directory.Delete (outDirForDll, recursive: true);

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.

🤖 ❌ MSBuild tasks — Invalidate the imported-library cache when deleting this directory. Consider a direct DLL reference containing only __AndroidNativeLibraries__.zip, then replace that DLL at the same path with one containing no Android content, without changing the project or AAR inputs. The JAR/resource/asset outputs are empty both times, and extractedDirectories still contains the identical entry added at line 168. Consequently libraryprojectimports.cache is unchanged and SaveIfChanged preserves its timestamp. _BuildLibraryImportsCache can then skip, leaving libraryimports.cache with NativeLibraries paths pointing into the directory just deleted here; _GetLibraryImports reintroduces those nonexistent files as AndroidNativeLibrary items.

Ensure this cleanup changes the cached extraction state or explicitly invalidates _BuildLibraryImportsCache. Please cover the native-only → no-content transition through both cache tasks/the incremental build pipeline; the current transition test includes JARs/resources, which change the first cache and hide this case.

Rule: Incremental-build correctness; invalidate downstream caches when removing outputs.

</ItemGroup>
<ItemGroup Condition=" '@(_AndroidAarLibraryImport->Count())' != '0' ">
<LibraryEnvironments
Include="%(_AndroidAarLibraryImport._AndroidImportsDirectory).net/env/**"

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.

🤖 💡 Testing — Could we add an automated regression for the .net/env/ cache-hit fix? The existing library-content test checks the environment value after the initial build, while the new incremental fixtures contain no environment entry. Extend that coverage with a timestamp-only AAR change that runs _AndroidExtractAarLibraries but skips re-extraction, then assert that the library environment value still reaches the generated runtime configuration. This would specifically catch the dropped-environment behavior removed from the old cache-hit branch.

Rule: Bug fixes need regression tests covering the affected incremental path.

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.

2 participants