[Xamarin.Android.Build.Tasks] merge targetSdkVersion into existing <uses-sdk> - #12290
Open
jonathanpeppers wants to merge 2 commits into
Open
[Xamarin.Android.Build.Tasks] merge targetSdkVersion into existing <uses-sdk>#12290jonathanpeppers wants to merge 2 commits into
targetSdkVersion into existing <uses-sdk>#12290jonathanpeppers wants to merge 2 commits into
Conversation
…`<uses-sdk>` When a project's `AndroidManifest.xml` already declared a `<uses-sdk />` element that omitted `android:targetSdkVersion`, `ManifestDocument` computed the value from `$(TargetSdkVersion)` for its own internal bookkeeping, emitted a `<!-- suppress UsesMinSdkAttributes -->` lint comment, and then never wrote the attribute onto the element. `aapt2` silently defaults `targetSdkVersion` to `minSdkVersion`, so the app shipped with an unintentionally ancient `targetSdk`. This was inconsistent with the adjacent branch, which already merges `android:minSdkVersion` into a user-authored `<uses-sdk />`. The omission and the suppression comment arrived wholesale with the original monodroid import and were never revisited. Write `android:targetSdkVersion` when the user did not specify one, and drop the now-dead lint suppression comment. User-specified values still win: both merges only fire when the attribute is absent. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf2958c-0b23-4fb1-9314-be9a82e8fd17
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a long-standing manifest merge inconsistency in ManifestDocument: when a user-authored <uses-sdk /> element omits android:targetSdkVersion, the build now explicitly writes the computed target SDK instead of relying on aapt2’s silent defaulting behavior. This prevents apps from accidentally shipping with an unintended (often very low) targetSdkVersion when only minSdkVersion was specified.
Changes:
- Update
ManifestDocument.Merge()to setandroid:targetSdkVersionon an existing<uses-sdk />element when it’s missing, and remove the dead “suppress UsesMinSdkAttributes” comment emission. - Add new in-process unit coverage (
ManifestDocumentTest) validating all four<uses-sdk />shapes and asserting the suppression comment is no longer produced. - Refactor the
MockVersionResolverhelper so it can be shared (removing the nested copy fromManifestTest).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs | Ensures missing android:targetSdkVersion is merged into an existing user <uses-sdk />, avoiding unintended aapt2 defaults. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestTest.cs | Removes the now-duplicated nested MockVersionResolver helper in favor of a shared one. |
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestDocumentTest.cs | Adds focused in-process tests covering <uses-sdk /> merge behavior for min/target combinations and verifies suppression comment removal. |
Suppressed comments (1)
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/ManifestDocumentTest.cs:31
- 💡 suggestion: Repo conventions prefer C# raw string literals (
""") for multi-line strings.CreateManifestcan use a raw string literal here to avoid the@-string and escaping, and it stays easier to edit.
static string CreateManifest (string usesSdk) => $@"<?xml version=""1.0"" encoding=""utf-8""?>
<manifest xmlns:android=""http://schemas.android.com/apk/res/android"" package=""com.xamarin.usessdk"">
{usesSdk}
<application android:label=""UsesSdk"">
</application>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7bf2958c-0b23-4fb1-9314-be9a82e8fd17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
When a project's
AndroidManifest.xmlalready declares a<uses-sdk />element,ManifestDocumentmergesandroid:minSdkVersioninto it if absent — but it never mergedandroid:targetSdkVersion. Instead it computed the value purely for internal bookkeeping (targetSdkVersionValue, which drives<instrumentation>, permission, andscreenOrientationcodegen) and emitted a<!-- suppress UsesMinSdkAttributes -->lint comment:aapt2silently defaultstargetSdkVersiontominSdkVersionwhen the attribute is missing. So any app with a partial hand-written<uses-sdk />shipped with an unintentionally ancienttargetSdk, with no warning anywhere in the build.The motivating example
tests/CodeGen-Binding/Xamarin.Android.JcwGen-Tests/Properties/AndroidManifest.xmldeclared:$(TargetSdkVersion)was 37. Installing the resulting.apkon a physical Pixel 10 (API 36):Android 15+ shows the end user an "app built for an older version of Android" warning dialog on install when
targetSdkis low enough — which is how this was noticed.Why was it like this?
I looked for a deliberate "respect the user's
<uses-sdk />verbatim" policy and could not find one:git log -S 'suppress UsesMinSdkAttributes' -- .../ManifestDocument.csreturns exactly one commit:59ec488b4 [Xamarin.Android.Build.Tasks] Import from monodroid/301e7238. The behavior predates this repo entirely.a66013b5c,941e04c22,3ab04df75) only re-pointed where the value comes from. None revisited the "don't write it" decision.UsesMinSdkAttributesis an Android lint check id ("Minimum SDK and target SDK attributes not defined"). The comment is a symptom of the omission, not an independent feature — and<!-- suppress ... -->is IntelliJ inspection syntax, whereas Androidlinthonourstools:ignore, so it was likely never effective for ourLinttask anyway. The string appears exactly once in the whole repo: the line that writes it. Nothing reads it.minSdkVersioninto a user-authored element. ThetargetSdkVersionomission is an inconsistency, not a design.The change
Write
android:targetSdkVersionwhen the user did not specify one, and drop the now-dead suppression comment. User-specified values still win — both merges only fire when the attribute isnull.This changes the produced manifest for any customer who hand-wrote a partial
<uses-sdk />(min only, or neither attribute). Those apps will now get an explicittargetSdkVersionequal to$(TargetSdkVersion)whereaapt2previously defaulted it tominSdkVersion.That is the point of the fix — apps stop silently shipping with an ancient
targetSdk— but it is a real behavioral change at runtime: platform behavior changes gated ontargetSdknow apply to those apps. Customers who genuinely want a lowertargetSdkcan still setandroid:targetSdkVersionexplicitly (or$(TargetSdkVersion)); those values continue to win.Tests
New
ManifestDocumentTestwith fast in-process coverage of all four<uses-sdk />shapes, plus an assertion that the suppression comment is gone:minSdkVersiontargetSdkVersionNoUsesSdkElementMinSdkVersionOnlyandroid:minSdkVersion="24"24TargetSdkVersionOnlyandroid:targetSdkVersion="30"30MinAndTargetSdkVersion2430Verified the tests actually catch the bug: with the
ManifestDocument.cschange reverted,MinSdkVersionOnlyfails withExpected: "37" But was: null. With the fix, all 4 pass, as does the existingManifestTest.TargetSdkVersion_361In_36Out.MockVersionResolvermoved from aprivatenested class inManifestTestto namespace scope so both fixtures can share it; theManifestTest.csdiff is removal-only.