[Java.Interop] Bump generator's minimum API level to 24 and wire it through a CLI option - #12682
Open
simonrozsival wants to merge 5 commits into
Open
[Java.Interop] Bump generator's minimum API level to 24 and wire it through a CLI option#12682simonrozsival wants to merge 5 commits into
simonrozsival wants to merge 5 commits into
Conversation
…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>
…hardcoded constant Follow-up to #12681, which bumped the generator's hardcoded `const int MINIMUM_API_LEVEL = 24;` in SourceWriterExtensions.cs, relying on a comment to keep it in sync with `$(AndroidMinimumDotNetApiLevel)` in Configuration.props. That kind of manual sync is fragile and can silently drift, so this replaces the constant with a proper generator CLI input. Changes, following the existing option-plumbing pattern used by `--product-version`/`ProductVersion`: - CodeGeneratorOptions.cs: new `--minimum-api-level=LEVEL` CLI option and `MinimumApiLevel` property (defaults to 24 for standalone Java.Interop use). - CodeGenerationOptions.cs: matching runtime `MinimumApiLevel` property (defaults to 24). - CodeGenerator.cs: copies `options.MinimumApiLevel` into the constructed `CodeGenerationOptions`. - SourceWriterExtensions.cs: removes the `MINIMUM_API_LEVEL` constant; `AddSupportedOSPlatform`/`AddObsoletedOSPlatformAttribute` now read `opt.MinimumApiLevel` instead. `AddUnsupportedOSPlatform` is untouched, as it never referenced this constant. - Mono.Android.targets: `_GenerateBinding` now passes `--minimum-api-level=$(AndroidMinimumDotNetApiLevel)` to the generator, the same way `--api-level=$(AndroidApiLevel)` is passed today. Before: ```csharp // Must match $(AndroidMinimumDotNetApiLevel) in Configuration.props. const int MINIMUM_API_LEVEL = 24; ... if (since > MINIMUM_API_LEVEL && ...) ``` After: ```csharp if (since > opt.MinimumApiLevel && ...) ```// wired from `Mono.Android.targets` via `--minimum-api-level=$(AndroidMinimumDotNetApiLevel)`. Also adds `SupportedOSPlatformRespectsMinimumApiLevelOverride` to CodeGeneratorTests.cs, proving the override actually changes emitted attributes end-to-end (not just that the default matches the old constant). Test results (JAVA_HOME=jdk-21, dotnet test tests/generator-Tests/generator-Tests.csproj): - Targeted filter (ObsoletedOSPlatform|SupportedOSPlatform|UnsupportedOSPlatform): 25/25 passed - Full suite: 497/497 passed (493 previously + 4 new override test cases) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
simonrozsival
changed the base branch from
simonrozsival-generator-min-api-24
to
main
September 4, 2026 09:52
Contributor
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The change is low-risk plumbing with targeted test coverage confirming the new option is honored during code generation.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
external/Java.Interop/tools/generator/CodeGeneratorOptions.cs — --minimum-api-level currently accepts any integer value; negative/zero values will produce… |
What changed in this PR
This PR removes the generator’s hardcoded minimum Android API floor for [SupportedOSPlatform] / [ObsoletedOSPlatform] decisions and wires that floor through a new generator CLI option, fed from $(AndroidMinimumDotNetApiLevel) during the Mono.Android bindings generation.
Changes:
- Add
--minimum-api-level=LEVELto the generator CLI and thread it intoCodeGenerationOptions. - Replace the previous
MINIMUM_API_LEVELconstant usage withopt.MinimumApiLevelin the source writers. - Update generator unit tests to verify overriding
MinimumApiLevelchanges[SupportedOSPlatform]emission as expected.
| File | Description |
|---|---|
| external/Java.Interop/tools/generator/CodeGeneratorOptions.cs | Adds the CLI option and option storage for minimum API level. |
| external/Java.Interop/tools/generator/CodeGenerationOptions.cs | Adds runtime option (MinimumApiLevel) consumed by codegen logic. |
| external/Java.Interop/tools/generator/CodeGenerator.cs | Wires CLI options into CodeGenerationOptions for code generation. |
| external/Java.Interop/tools/generator/SourceWriters/Extensions/SourceWriterExtensions.cs | Replaces constant-based checks with opt.MinimumApiLevel in attribute emission logic. |
| src/Mono.Android/Mono.Android.targets | Passes --minimum-api-level=$(AndroidMinimumDotNetApiLevel) to generator invocation. |
| external/Java.Interop/tests/generator-Tests/Unit-Tests/CodeGeneratorTests.cs | Adds coverage proving the override is respected end-to-end in generation. |
Raising the generator's minimum API level from 21 to 24 stops emitting `[SupportedOSPlatform]` for APIs available since 22-24 and swaps `[ObsoletedOSPlatform]` for a plain `[Obsolete]` when the deprecation happened at or below API 24. Both are intentional: .NET for Android already requires API 24 at minimum, so those annotations were describing constraints that can never fail. The committed reference assembly still has the attributes, so ApiCompat reports 5547 `CannotRemoveAttribute` issues (4906 for `SupportedOSPlatformAttribute`, 641 for `ObsoletedOSPlatformAttribute`). Removing either attribute is not a source or binary break for consumers - it only relaxes CA1416 and obsoletion diagnostics - so baseline them. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Wiring `--minimum-api-level` into `AddObsoletedOSPlatformAttribute` as well
meant anything deprecated in API 22-24 switched from `[ObsoletedOSPlatform]`
to a plain `[Obsolete]`. That is a source-breaking change: it downgrades a
suppressible analyzer diagnostic (CA1422) into CS0618, which is an error for
anyone building with `$(TreatWarningsAsErrors)`. Two lanes caught it:
* MAUI failed to pack, because `ExifInterface.TagIso` is deprecated-since 24:
ImageProcessor.android.cs(258,6): Error CS0618:
'ExifInterface.TagIso' is obsolete: 'deprecated'
* All ten macOS `MSBuild` lanes failed `CodeBehindTests`, because
`CommonSampleLibrary` calls `TextView.SetTextAppearance (Context, int)`
(deprecated-since 23). That project is referenced by every CodeBehind
test project, so it failed before the app was ever compiled - which is why
the `FailedBuild_Conflicting*` tests reported a missing CS0266.
The `[SupportedOSPlatform]` floor is a different question: omitting an
attribute only ever removes diagnostics, so that one still follows
`$(AndroidMinimumDotNetApiLevel)`. Keep the obsoletion floor at 21 and rename
the constant so the distinction is explicit.
Also drop the 641 `ObsoletedOSPlatformAttribute` entries from the api-compat
baseline, since those attributes are no longer removed and `--validate-baseline`
rejects entries that do not correspond to a real issue.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep SupportedOSPlatform and ObsoletedOSPlatform emission coupled to the configured minimum API level. Once the product minimum is API 24, consumers will receive the same deprecated-API diagnostic regardless of whether an API deprecated in API 22-24 is represented by ObsoletedOSPlatform or Obsolete, so a separate API 21 floor adds complexity without preserving compatibility. Restore the corresponding generator expectation and ApiCompat baseline entries. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
rolfbjarne
approved these changes
Sep 7, 2026
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.

This PR (previously split across #12681 and #12682, now combined into one) raises the Java.Interop generator's minimum supported Android API level to 24 for
[SupportedOSPlatform]/[ObsoletedOSPlatform]attribute emission, and wires that value through a proper CLI option instead of a hardcoded constant.Motivation
APIs available at or below the minimum supported API level never need a
[SupportedOSPlatform]attribute, since they're always present. Likewise, APIs obsoleted at or below the minimum can use a regular[Obsolete]attribute instead of[ObsoletedOSPlatform]: once the product minimum is API 24, consumers receive a deprecated-API diagnostic either way, so keeping a separate API 21 floor would add complexity without preserving compatibility.The generator previously hardcoded this shared floor as
const int MINIMUM_API_LEVEL = 21;inSourceWriterExtensions.cs, below the$(AndroidMinimumDotNetApiLevel)of 24 that we actually ship. Keeping the values in sync by hand is fragile, so this PR replaces the constant with a real generator CLI input (--minimum-api-level) wired from$(AndroidMinimumDotNetApiLevel), following the same option-plumbing pattern already used for--product-version/ProductVersion.Changes
CodeGeneratorOptions.cs: new--minimum-api-level=LEVELCLI option andMinimumApiLevelproperty (defaults to24, preserving current standalone Java.Interop behavior).CodeGenerationOptions.cs: matching runtimeMinimumApiLevelproperty (defaults to24).CodeGenerator.cs: copiesoptions.MinimumApiLevelinto the constructedCodeGenerationOptions, alongsideProductVersionand friends.SourceWriterExtensions.cs:AddSupportedOSPlatformandAddObsoletedOSPlatformAttributenow readopt.MinimumApiLevelinstead of a hardcoded constant.AddUnsupportedOSPlatformis untouched — it never referenced this constant.Mono.Android.targets:_GenerateBindingnow passes--minimum-api-level=$(AndroidMinimumDotNetApiLevel)to the generator invocation, the same way--api-level=$(AndroidApiLevel)is passed today.$(AndroidMinimumDotNetApiLevel)is defined inConfiguration.propsand already used elsewhere in this same.targetsfile, confirming it's in scope.acceptable-breakages-vReference-net11.0.txt: baselines the intended platform-attribute removals against the committed reference assembly.Before
After
The value is now wired end-to-end from
Mono.Android.targetsvia--minimum-api-level=$(AndroidMinimumDotNetApiLevel), so generator output cannot drift from the product minimum.API compatibility baseline
Raising the floor from 21 to 24 intentionally changes generated attributes on APIs introduced or deprecated in API 22–24.
_CheckApiCompatibilitycomparesMono.Android.dllagainst the committed reference assembly intests/api-compatibility/reference/, so it reports 5,547CannotRemoveAttributeissues:SupportedOSPlatformAttributeinstances.ObsoletedOSPlatformAttributeinstances where generation now falls back to[Obsolete].These expected differences are recorded in
acceptable-breakages-vReference-net11.0.txt. The entries should disappear the next time the reference assembly is regenerated at a milestone.Tests
Added:
SupportedOSPlatformOmittedAtOrBelowMinimumApiLevel— confirms APIs at or below the default minimum don't get[SupportedOSPlatform].SupportedOSPlatformRespectsMinimumApiLevelOverride— overridesoptions.MinimumApiLevel(to 21 and 30) and confirms[SupportedOSPlatform]emission respects the override.Updated the existing obsoletion test to confirm an API deprecated at API 22 falls back to
[Obsolete]with the API 24 minimum.Ran with
JAVA_HOME=jdk-21,dotnet test tests/generator-Tests/generator-Tests.csproj: 497/497 passed, 0 failures.Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com