Skip to content

[Java.Interop] Bump generator's minimum API level for SupportedOSPlatform/Obsolete to 24 - #12681

Closed
simonrozsival wants to merge 1 commit into
mainfrom
simonrozsival-generator-min-api-24
Closed

[Java.Interop] Bump generator's minimum API level for SupportedOSPlatform/Obsolete to 24#12681
simonrozsival wants to merge 1 commit into
mainfrom
simonrozsival-generator-min-api-24

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

What

Bumps the Java.Interop generator's MINIMUM_API_LEVEL constant (in external/Java.Interop/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs) from 21 to 24, so it matches .NET for Android's actual minimum supported Android API level.

-const int MINIMUM_API_LEVEL = 21;
+// Must match $(AndroidMinimumDotNetApiLevel) in Configuration.props.
+const int MINIMUM_API_LEVEL = 24;

Why

This constant controls the floor below which [SupportedOSPlatform] (and the equivalent [ObsoletedOSPlatform] early-obsolete fallback) is considered redundant and omitted from generated bindings. It was still 21, a leftover from before .NET for Android raised its floor. The authoritative source of truth is $(AndroidMinimumDotNetApiLevel) in Configuration.props, which is 24 and must match $(AndroidApiLevelMin) in dotnet/runtime's Directory.Build.props.

Since every API >= 21 is always available on every currently supported target (API 24+), emitting [SupportedOSPlatform ("android21.0"/"android22.0"/"android23.0")] on such members is dead weight — the attribute can never influence analyzer diagnostics or trimming, it only adds generated source text and IL/metadata. Bumping the floor to 24 removes these now-redundant attributes while still correctly emitting [SupportedOSPlatform] for APIs 25+.

I looked for an existing small, clean generator input (e.g. a CLI option) that already threads a "minimum API" value through the generator so this constant could just reuse it, but found none — CodeGenerationOptions and the generator CLI have no such parameter today. Per the scope of this change, a surgical constant bump is the right fix rather than introducing new wiring.

[UnsupportedOSPlatform] is intentionally unchanged

AddUnsupportedOSPlatform only checks since > 0 and never references MINIMUM_API_LEVEL — "removed-since" semantics are structurally independent of this floor. There's nothing to change there, and this PR doesn't touch it.

Measurement (real, controlled generator invocation)

Built the current generator.dll and ran it directly against the real, committed src/Mono.Android/Profiles/api-37.2.xml plus the real Android SDK's api-versions.xml (android-37.2 platform), using the exact CLI flags the _GenerateBinding MSBuild target uses (from src/Mono.Android/Mono.Android.targets) — once with MINIMUM_API_LEVEL=21 (baseline, via a temporary local revert) and once with =24 (this change):

Level Baseline (21) New (24) Removed
android22 475 0 475
android23 5,808 0 5,808
android24 13,971 0 13,971
Total 84,978 64,724 20,254

(android25+ counts are unaffected, e.g. android25 stayed at 364 in both runs.)

So this removes 20,254 redundant [SupportedOSPlatform] attribute applications from the generated Mono.Android API-37 bindings. Using the same per-attribute CustomAttribute-table-row + constructor-argument-blob cost estimate used in prior analysis of this class of change (~12 bytes/attribute), this is roughly ~243 KB of estimated metadata savings. A full Mono.Android.dll build + exact metadata/ildasm diff was not performed (it requires a full product build, and this environment is shared with concurrent builds from other sessions); the counts above come from directly invoking the actual, current generator against real generator inputs — the approach the task explicitly allows as an alternative to a full build. Note these exact counts differ somewhat from an earlier estimate (396/4,877/12,519 = 17,792) quoted for this change; the real, reproducible counts here are higher across all three levels, likely due to differences in the specific API-versions/profile XML snapshot used for the earlier estimate.

Tests

In external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs:

  • Added SupportedOSPlatformOmittedAtOrBelowMinimumApiLevel, parametrized for android22/23/24 (must not emit [SupportedOSPlatform]) and android25 (must emit it) — directly exercising the new floor boundary.
  • Fixed ObsoletedOSPlatformAttributeUnneededSupport: members with deprecated-since='22' now correctly fall back to plain [Obsolete] (since 22 <= 24) instead of [ObsoletedOSPlatform ("android22.0")] (which was correct only under the old 22 <= 21 == false condition). I verified via the actual generated output that the fallback still correctly preserves the raw Android deprecation message text (the message-clearing logic for the literal string "deprecated" only runs in the [ObsoletedOSPlatform] branch, so this needed to be checked precisely rather than assumed).

Results:

  • Targeted filter (ObsoletedOSPlatform|SupportedOSPlatform|UnsupportedOSPlatform): 21/21 passed
  • Full generator-Tests suite: 493/493 passed

No other generator test fixtures reference SupportedOSPlatform/ObsoletedOSPlatform text for API levels 21–24 that would need updating.

Scope

This is a single, surgical constant change plus the test updates it requires — no other generator behavior, MSBuild targets, or product code were touched.

…o 24

The Java.Interop generator's `MINIMUM_API_LEVEL` constant controlled
the floor below which `[SupportedOSPlatform]` (and the equivalent
`[ObsoletedOSPlatform]` early-obsolete fallback) is considered
redundant and therefore omitted. It was still set to 21, but
.NET for Android's actual minimum supported Android API level is
24 (`$(AndroidMinimumDotNetApiLevel)` in `Configuration.props`,
which must match `$(AndroidApiLevelMin)` in dotnet/runtime).

Because every API >= 21 is always available on every currently
supported target (API 24+), emitting `[SupportedOSPlatform
("android21.0"/"android22.0"/"android23.0")]` for such members is
dead weight: the attribute can never affect analyzer diagnostics
or trimming decisions, it only adds IL/metadata for the compiler
to skip. Bumping the constant to 24 removes these now-redundant
attributes while still emitting `[SupportedOSPlatform]` correctly
for APIs 25+.

`[UnsupportedOSPlatform]` is intentionally left untouched:
`AddUnsupportedOSPlatform` only checks `since > 0` and does not
reference `MINIMUM_API_LEVEL` at all, so "removed-since" semantics
are structurally independent of this floor and remain fully
correct.

Measurement (real, controlled generator invocation):
Built the current `generator.dll` and ran it directly against the
real, committed `src/Mono.Android/Profiles/api-37.2.xml` plus the
real Android SDK's `api-versions.xml` (android-37.2), using the
exact CLI flags `_GenerateBinding` uses in
`src/Mono.Android/Mono.Android.targets`, once with
MINIMUM_API_LEVEL=21 (baseline) and once with =24 (this change):

| Level     | Baseline (21) | New (24) | Removed |
|-----------|---------------|----------|---------|
| android22 | 475           | 0        | 475     |
| android23 | 5,808         | 0        | 5,808   |
| android24 | 13,971        | 0        | 13,971  |
| **Total** | 84,978        | 64,724   | **20,254** |

So this change removes 20,254 redundant `[SupportedOSPlatform]`
attribute applications from the generated Mono.Android API-37
bindings (android25+ counts, e.g. android25=364, are unaffected).
Using the same per-attribute CustomAttribute-table-row +
constructor-argument-blob cost estimate previously used for this
class of change (~12 bytes/attribute), this corresponds to an
estimated ~243 KB of metadata savings. A full Mono.Android.dll
build/ildasm diff was not performed (would require a full product
build); the count above comes from directly invoking the real,
current generator against real generator inputs, which the task
explicitly allows as an alternative to a full build.

Test changes (external/Java.Interop/tests/generator-Tests):
* Added `SupportedOSPlatformOmittedAtOrBelowMinimumApiLevel`,
  parametrized for android22/23/24 (must NOT emit
  `[SupportedOSPlatform]`) and android25 (must emit it), directly
  exercising the new floor.
* Fixed `ObsoletedOSPlatformAttributeUnneededSupport`: members with
  `deprecated-since='22'` now fall back to plain `[Obsolete]`
  (since 22 <= 24) instead of `[ObsoletedOSPlatform ("android22.0")]`
  (since 22 <= 21 was false under the old floor). Verified the
  fallback correctly preserves the raw Android deprecation message.

Verified: targeted filter (`ObsoletedOSPlatform|SupportedOSPlatform|
UnsupportedOSPlatform`, 21/21) and the full `generator-Tests` suite
(493/493) pass.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 4, 2026 09:40

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.

Copilot review overview

🟢 Approval recommended

The change is small, consistent with repo configuration (API 24), and the updated tests cover the new boundary behavior.

Review tier: Lite
Findings: 1 Low severity

New issues introduced by this change (1)
Severity Finding
Low severity external/​Java.Interop/​tests/​generator-Tests/​Unit-Tests/​CodeGeneratorTests.cs — 💡 Consider deriving the minimum API level from the generator (and asserting it matches…
What changed in this PR

This PR updates the Java.Interop generator’s notion of the minimum supported Android API level so it stops emitting redundant platform attributes for APIs that are always present on supported .NET for Android targets.

Changes:

  • Bumped MINIMUM_API_LEVEL from 21 → 24 in the generator’s SourceWriterExtensions to align with $(AndroidMinimumDotNetApiLevel).
  • Updated/expanded generator unit tests to validate [SupportedOSPlatform] omission at/below the new floor and adjusted [ObsoletedOSPlatform] vs [Obsolete] expectations accordingly.
File Description
external/​Java.Interop/​tools/​generator/​SourceWriters/​Extensions/​SourceWriterExtensions.cs Raises the generator floor to API 24 so [SupportedOSPlatform]/[ObsoletedOSPlatform] aren’t emitted when they can’t affect supported builds.
external/​Java.Interop/​tests/​generator-Tests/​Unit-Tests/​CodeGeneratorTests.cs Updates and adds tests to cover the new API-floor boundary behavior for platform attributes.

Comment on lines +1456 to +1479
[Test]
// MINIMUM_API_LEVEL is 24 (matches $(AndroidMinimumDotNetApiLevel) in Configuration.props), so
// there's no sense writing [SupportedOSPlatform] for an API available at or below that floor:
// it's available in every version we support. Only API levels above the floor need it.
[TestCase (22, false)]
[TestCase (23, false)]
[TestCase (24, false)]
[TestCase (25, true)]
public void SupportedOSPlatformOmittedAtOrBelowMinimumApiLevel (int apiLevel, bool expectAttribute)
{
var klass = SupportTypeBuilder.CreateClass ("java.code.MyClass", options);
klass.ApiAvailableSince = new AndroidSdkVersion (apiLevel);

generator.Context.ContextTypes.Push (klass);
generator.WriteType (klass, string.Empty, new GenerationInfo ("", "", "MyAssembly"));
generator.Context.ContextTypes.Pop ();

var attribute = $"[global::System.Runtime.Versioning.SupportedOSPlatformAttribute (\"android{apiLevel}.0\")]";

if (expectAttribute)
StringAssert.Contains (attribute, builder.ToString (), $"Should contain SupportedOSPlatform for android{apiLevel}!");
else
StringAssert.DoesNotContain (attribute, builder.ToString (), $"Should NOT contain SupportedOSPlatform for android{apiLevel}!");
}
@simonrozsival

Copy link
Copy Markdown
Member Author

Closing in favor of #12682, which now contains both this commit and the follow-up wiring change as a single combined PR against main.

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