Skip to content

[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
mainfrom
simonrozsival-generator-min-api-level-wiring
Open

[Java.Interop] Bump generator's minimum API level to 24 and wire it through a CLI option#12682
simonrozsival wants to merge 5 commits into
mainfrom
simonrozsival-generator-min-api-level-wiring

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Sep 4, 2026

Copy link
Copy Markdown
Member

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; in SourceWriterExtensions.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=LEVEL CLI option and MinimumApiLevel property (defaults to 24, preserving current standalone Java.Interop behavior).
  • CodeGenerationOptions.cs: matching runtime MinimumApiLevel property (defaults to 24).
  • CodeGenerator.cs: copies options.MinimumApiLevel into the constructed CodeGenerationOptions, alongside ProductVersion and friends.
  • SourceWriterExtensions.cs: AddSupportedOSPlatform and AddObsoletedOSPlatformAttribute now read opt.MinimumApiLevel instead of a hardcoded constant. AddUnsupportedOSPlatform is untouched — it never referenced this constant.
  • Mono.Android.targets: _GenerateBinding now passes --minimum-api-level=$(AndroidMinimumDotNetApiLevel) to the generator invocation, the same way --api-level=$(AndroidApiLevel) is passed today. $(AndroidMinimumDotNetApiLevel) is defined in Configuration.props and already used elsewhere in this same .targets file, confirming it's in scope.
  • acceptable-breakages-vReference-net11.0.txt: baselines the intended platform-attribute removals against the committed reference assembly.

Before

const int MINIMUM_API_LEVEL = 21;
...
if (since > MINIMUM_API_LEVEL && opt.CodeGenerationTarget == ...)

After

if (since > opt.MinimumApiLevel && opt.CodeGenerationTarget == ...)

The value is now wired end-to-end from Mono.Android.targets via --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. _CheckApiCompatibility compares Mono.Android.dll against the committed reference assembly in tests/api-compatibility/reference/, so it reports 5,547 CannotRemoveAttribute issues:

  • 4,906 removed SupportedOSPlatformAttribute instances.
  • 641 removed ObsoletedOSPlatformAttribute instances 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 — overrides options.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

simonrozsival and others added 2 commits September 4, 2026 11:40
…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>
Copilot AI lite review requested due to automatic review settings September 4, 2026 09:50
@simonrozsival
simonrozsival changed the base branch from simonrozsival-generator-min-api-24 to main September 4, 2026 09:52
@simonrozsival simonrozsival changed the title [generator] Wire minimum API level through a CLI option instead of a hardcoded constant [Java.Interop] Bump generator's minimum API level to 24 and wire it through a CLI option Sep 4, 2026

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 low-risk plumbing with targeted test coverage confirming the new option is honored during code generation.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity 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=LEVEL to the generator CLI and thread it into CodeGenerationOptions.
  • Replace the previous MINIMUM_API_LEVEL constant usage with opt.MinimumApiLevel in the source writers.
  • Update generator unit tests to verify overriding MinimumApiLevel changes [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.

Comment thread external/Java.Interop/tools/generator/CodeGeneratorOptions.cs
simonrozsival and others added 2 commits September 4, 2026 14:01
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>
@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Sep 4, 2026
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>
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