Skip to content

Conversation

jonpryor
Copy link
Contributor

@jonpryor jonpryor commented Sep 4, 2025

[generator] Support major.minor API levels

Context: dotnet/android#10438
Context: dotnet/android#10438 (comment)

Android 16 Quarterly Platform Release 2 (QPR2) (API-CANARY) Beta 1
has been released. What makes API_CANARY unique in the history of
Android is that it is a "minor" SDK version:

  • <uses-sdk/>:

    It's not possible to specify that an app either targets or requires a minor SDK version.

  • Using new APIs with major and minor releases:

    The new SDK_INT_FULL constant can be used for API checks…

    if (SDK_INT_FULL >= VERSION_CODES_FULL.[MAJOR or MINOR RELEASE]) {
      // Use APIs introduced in a major or minor release
    }
    

    You can also use the Build.getMinorSdkVersion() method to
    get just the minor SDK version:

    minorSdkVersion = Build.getMinorSdkVersion(Build.VERSION_CODES_FULL.BAKLAVA);
    

This is not "API-37". This is "API-36.1"

This impacts everything:

  • [SupportedOSPlatform] should include the minor SDK value
  • [UnsupportedOSPlatform] should also include the minor SDK value.
  • generator --api-level=API-LEVEL should include the minor value

Add a new Java.Interop.Tools.Generator.ApiLevel type which holds
the Android SDK version, both major and minor values. This is a
value type which works like System.Int32/System.Version.

Replace all instances of int ApiLevel (and equivalent) with
ApiLevel ApiLevel (and equivalent).

Update [SupportedOSPlatform], [UnsupportedOSPlatform], and
[ObsoletedOSPlatform] attribute output to include the full SDK value.

@jonpryor jonpryor force-pushed the dev/jonpryor/jonp-minor-ApiLevels branch from 0c95aed to 2fae0ee Compare September 4, 2025 00:27
Context: dotnet/android#10438
Context: dotnet/android#10438 (comment)

Android 16 Quarterly Platform Release 2 (QPR2) (API-CANARY) Beta 1
has been released.  What makes API_CANARY unique in the history of
Android is that it is a "minor" SDK version:

  * [`<uses-sdk/>`][3]:

    > It's not possible to specify that an app either targets or requires a minor SDK version.

  * [Using new APIs with major and minor releases][4]:

    > The new [`SDK_INT_FULL`][5] constant can be used for API checks…
    >
    >     if (SDK_INT_FULL >= VERSION_CODES_FULL.[MAJOR or MINOR RELEASE]) {
    >       // Use APIs introduced in a major or minor release
    >     }
    >
    > You can also use the [`Build.getMinorSdkVersion()`][6] method to
    > get just the minor SDK version:
    >
    >     minorSdkVersion = Build.getMinorSdkVersion(Build.VERSION_CODES_FULL.BAKLAVA);

This is ***not*** "API-37".  This is "API-36.1"

This impacts *everything*:

  * `[SupportedOSPlatform]` should include the minor SDK value
  * `[UnsupportedOSPlatform]` should also include the minor SDK value.
  * `generator --api-level=API-LEVEL` should support the minor value
    and appropriately propagate the value.
  * …

Add a new `Java.Interop.Tools.Generator.AndroidSdkVersion` type which
holds the Android SDK version, both major (`ApiLevel)` and
minor values (`MinorRevision)`.  This is a value type which works
like `System.Int32`/`System.Version`.

Replace all instances of `int ApiLevel` (and equivalent) with
`AndroidSdkVersion ApiLevel` (and equivalent).

Update `[SupportedOSPlatform]`, `[UnsupportedOSPlatform]`, and
`[ObsoletedOSPlatform]` attribute output to include the full SDK value.

Update `NamingConverter.ParseApiLevel()` to support `MAJOR.MINOR`
values within a `//@merge.SourceFile` value.

*Note*: Changes to `NamingConverter.ParseApiLevel()` are largely a
nothing-burger because the dotnet/android side usually has metadata:

	<attr
	  api-since="36.1"
	  path="/api//*[contains(@merge.SourceFile,'api-CANARY.xml.in')]"
	  name="api-since">36.1</attr

which explicitly adds/sets `//@api-since` based on the
`//@merge.SourceFile` value.

[3]: https://developer.android.com/guide/topics/manifest/uses-sdk-element
[4]: https://developer.android.com/about/versions/16/features#using-new
[5]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT_FULL
[6]: https://developer.android.com/reference/android/os/Build#getMinorSdkVersion(int)
@jonpryor jonpryor force-pushed the dev/jonpryor/jonp-minor-ApiLevels branch from 2fae0ee to 225d748 Compare September 4, 2025 18:20
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Sep 4, 2025
Context: dotnet/java-interop#1360

`generator` changes to support `generator --api-level=36.1`.
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Sep 4, 2025
dotnet/java-interop#1360 allows `generator --api-level=36.1` to
conceptually work, which is needed because `$(AndroidApiLevel)`
is forwarded as `generator --api-level`.

Thus:

	./dotnet-local.sh build src/Mono.Android/*.csproj \
	  -p:AndroidApiLevel=36.1 -p:AndroidPlatformId=CANARY -p:AndroidFrameworkVersion=v16.1 \
	  -p:IsUnstableVersion=true -bl

There were a couple problems with the above:

Firstly, APIs added in API-CANARY weren't marked as
`[SupportedOSPlatform("android36.1")]`.  This was because
`metadata` needed to be updated to set `//@api-since` to 36.1, not 36.
Doh/oops.

Secondly, we need a new set of `PublicAPI.*.txt` files in order for
the Public API analyzer to "do its thing".  Add them.
`src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Shipped.txt`
is based on the union of `PublicAPI.Shipped.txt` and
`PublicApi.Unshipped.txt` for API-36.

The above `dotnet-local.sh` command now passes locally.

TODO: update API compat checks.  See also: bdc4ccc.
@jonathanpeppers
Copy link
Member

/azp run

Copy link

No pipelines are associated with this pull request.

@jonathanpeppers jonathanpeppers merged commit f07b538 into dotnet:main Sep 8, 2025
2 checks passed
jonpryor added a commit to jonpryor/xamarin-android that referenced this pull request Sep 8, 2025
jonathanpeppers pushed a commit to dotnet/android that referenced this pull request Sep 12, 2025
Context: https://developer.android.com/about/versions/16/qpr2/
Context: https://android-developers.googleblog.com/2025/08/android-16-qpr2-beta-1-is-here.html

Changes: dotnet/java-interop@a5d7370...f07b538

  * dotnet/java-interop@f07b5385: [generator] Support major.minor API levels (dotnet/java-interop#1360)

Android 16 Quarterly Platform Release 2 (QPR2) Beta 1 has been released.

  * [API-CANARY Beta 1 vs. API-36][0]

The Android 16 QPR2 Beta 1 [Announcement][1] suggests the following
timeline:

  * Aug/Sep: Unstable Betas
  * Oct: Stable beta with a [Platform Stability milestone][2]
  * ???: Final

Android 16 QPR2 adds an additional wrinkle: "minor" SDK versions are
now A Thing™; see:

  * [`<uses-sdk/>`][3]:

    > It's not possible to specify that an app either targets or requires a minor SDK version.

  * [Using new APIs with major and minor releases][4]:

    > The new [`SDK_INT_FULL`][5] constant can be used for API checks…
    >
    >     if (SDK_INT_FULL >= VERSION_CODES_FULL.[MAJOR or MINOR RELEASE]) {
    >       // Use APIs introduced in a major or minor release
    >     }
    >
    > You can also use the [`Build.getMinorSdkVersion()`][6] method to
    > get just the minor SDK version:
    >
    >     minorSdkVersion = Build.getMinorSdkVersion(Build.VERSION_CODES_FULL.BAKLAVA);

What these mean is that "Android 16 QPR2" *will not* be API-37.
It will be "API-36.1": `<uses-sdk/>` will specify e.g.
`android:targetSdkVersion="36"` (***not*** `37`), and runtime guards
against Android 16 QPR2 will need to check against e.g.
[`Build.VERSION_CODES_FULL#BAKLAVA_1`][7], documented as "Android 36.1".

This in turn means that our historical approach to adding new API
levels does not immediately apply.  API-CANARY *will not be* API-37.
`net10.0-android37.0` -- when it exists -- will not bind API-CANARY;
it will bind Android 17, presumably in 2026.

So what do we call it?

We will need to call it `net10.0-android36.1`:

  * `net10.0-android36.0` is "plain"/"original" API-36.
  * `net10.0-android36.1` will be Android 16 QPR2 / API-CANARY.

This will be usable in its preview form to `main` users
who explicitly target `net10.0-android36.1`.

This is unlikely to be released in .NET 10 GA (2025-Nov) as
`net10.0-android36.1`.

~~ Changes: Minor SDK Versions ~~

"Minor" SDK versions are something we've never had to support before,
so lots of associated changes to the build system and tooling are
required to support it.  Among the changes is the assumption that an
API level is "just" an int.

Update `create-android-api.csproj` to use `java-source-utils.jar`
instead of `param-name-importer.dll`, as `param-name-importer.dll`
was not able to import API-CANARY's `android-stubs-src.jar`:

	create-android-api failed with 2 error(s) (1.0s)
	  EXEC : error (3050: 47) Invalid character: '''.

More specifically:

	% dotnet bin/Debug/lib/packs/Microsoft.Android.Sdk.Darwin/36.0.0/tools/param-name-importer.dll \
	  "-source-stub-zip=$HOME/android-toolchain/sdk/platforms/android-CANARY/android-stubs-src.jar" \
	  "-output-text=src/Mono.Android/Profiles/api-CANARY.params.txt" \
	  -verbose -framework-only
	…
	java/lang/Character.java
	Error (3050:47) Invalid character: '''.

As `param-name-importer.dll` uses an Irony-based Java source parser,
and *we have a better* Java source parser which can *also* output the
`param-name-importer.dll` text format in `java-source-utils.jar`
use `java-source-utils.jar` instead.

Update `GenerateSupportedPlatforms.cs` to look at
`AndroidVersion.VersionCodeFull` instead of `AndroidVersion.ApiLevel`.
This allows minor SDK versions to be present in
`@(AndroidSdkSupportedTargetPlatformVersion)`.

Update `GeneratedMonoAndroidProjitemsFile.cs` so that the generated
`bin\Build$(Configuration)\Mono.Android.Apis.projitems` file contains
`%(AndroidApiInfo.VersionCodeFull)` item metadata.

Update `Mono.Android.targets` so that the generated `AndroidApiInfo.xml`
file contains `<VersionCodeFull/>`, based on `%(VersionCodeFull)`.

Add `InstallAndRunTests.DotNetInstallAndRunPreviewAPILevels()`, which
uses the new `net10.0-android36.1` target framework and references
one of the new Preview APIs.

Add a new set of `PublicAPI.*.txt` files so that the Public API
analyzer can "do its thing".
`src/Mono.Android/PublicAPI/API-36.1/PublicAPI.Shipped.txt`
is based on the union of `PublicAPI.Shipped.txt` and
`PublicApi.Unshipped.txt` for API-36.

Update the workloads to use `Microsoft.Android.Runtime.Mono.36.[RID]`
packs.  Fixes:

	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(119,5): error NETSDK1181: Error getting pack version: Pack 'Microsoft.Android.Runtime.Mono.36.1.android-arm64' was not present in workload manifests.
	…/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets(119,5): error NETSDK1181: Error getting pack version: Pack 'Microsoft.Android.Runtime.Mono.36.1.android-x64' was not present in workload manifests.

Update `XABuildConfig` so that all `*ApiLevel` fields are now
`System.Version`, ***not*** `int`.  This allows them to properly
contain minor values.  This in turn impacts "everything", changing
many `int`s to `Versions`s.

Additionally, update `XASdkTests.DotNetTargetFrameworks` to *not*
always append `.0`.  The use of `Version` handles this for us, nicely.

Update `build.gradle` generation within unit tests to ensure that
`compileSdk` et. al are `int` values.

~~ Changes: Disable lint checks for `@(AndroidGradleProject)` Tests ~~

Some `@(AndroidGradleProject)` tests were failing on CI:

	…/Microsoft.Android.Sdk.Bindings.Gradle.targets(83,5): error XAGRDL0000: FAILURE: Build failed with an exception.
	…/Microsoft.Android.Sdk.Bindings.Gradle.targets(83,5): error XAGRDL0000:
	…/Microsoft.Android.Sdk.Bindings.Gradle.targets(83,5): error XAGRDL0000: * What went wrong:
	…/Microsoft.Android.Sdk.Bindings.Gradle.targets(83,5): error XAGRDL0000: Execution failed for task ':TestModule:lintVitalAnalyzeRelease'.
	…/Microsoft.Android.Sdk.Bindings.Gradle.targets(83,5): error XAGRDL0000: > A failure occurred while executing com.android.build.gradle.internal.lint.AndroidLintWorkAction
	…/Microsoft.Android.Sdk.Bindings.Gradle.targets(83,5): error XAGRDL0000:    > For input string: "36.0"

After a fair bit of digging around -- read the `.binlog`, find
the `gradlew` invocation, *manually* run the `gradlew` invocation
while adding `-d` go get diagnostic output, which in turn provides
the `lint` invocation -- we see that `lint` is crashing with:

	Exception in thread "main" java.lang.NumberFormatException: For input string: "36.0"
		at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
		at java.base/java.lang.Integer.parseInt(Integer.java:668)
		at java.base/java.lang.Integer.parseInt(Integer.java:786)
		at com.android.tools.lint.client.api.SimplePlatformLookup$Companion.platformFromSourceProp(SimplePlatformLookup.kt:256)
		at com.android.tools.lint.client.api.SimplePlatformLookup$Companion.addPlatforms(SimplePlatformLookup.kt:146)
		at com.android.tools.lint.client.api.SimplePlatformLookup$Companion.access$addPlatforms(SimplePlatformLookup.kt:113)
		at com.android.tools.lint.client.api.SimplePlatformLookup.<init>(SimplePlatformLookup.kt:65)
		at com.android.tools.lint.client.api.SimplePlatformLookup$Companion.get(SimplePlatformLookup.kt:118)
		at com.android.tools.lint.client.api.LintClient.getPlatformLookup(LintClient.kt:1045)
		at com.android.tools.lint.client.api.LintClient.getCompileTarget(LintClient.kt:1063)
		at com.android.tools.lint.detector.api.Project.getBuildTarget(Project.java:959)
		at com.android.tools.lint.LintCliClient$pickBuildTarget$2.invoke(LintCliClient.kt:1464)
		at com.android.tools.lint.LintCliClient$pickBuildTarget$2.invoke(LintCliClient.kt:1464)
		at kotlin.sequences.TransformingSequence$iterator$1.next(Sequences.kt:210)
		at kotlin.sequences.FilteringSequence$iterator$1.calcNext(Sequences.kt:170)
		at kotlin.sequences.FilteringSequence$iterator$1.hasNext(Sequences.kt:194)
		at com.android.tools.lint.LintCliClient.pickBuildTarget(LintCliClient.kt:1947)
		at com.android.tools.lint.LintCliClient.getBootClassPath(LintCliClient.kt:1443)
		at com.android.tools.lint.Main$MainLintClient.getBootClassPath(Main.java:805)
		at com.android.tools.lint.LintCliClient.initializeProjects(LintCliClient.kt:1421)
		at com.android.tools.lint.client.api.LintClient.performInitializeProjects$lint_api(LintClient.kt:1009)
		at com.android.tools.lint.client.api.LintDriver.initializeProjectRoots(LintDriver.kt:569)
		at com.android.tools.lint.client.api.LintDriver.doAnalyze(LintDriver.kt:484)
		at com.android.tools.lint.client.api.LintDriver.analyzeOnly(LintDriver.kt:443)
		at com.android.tools.lint.LintCliClient$analyzeOnly$1.invoke(LintCliClient.kt:233)
		at com.android.tools.lint.LintCliClient$analyzeOnly$1.invoke(LintCliClient.kt:233)
		at com.android.tools.lint.LintCliClient.run(LintCliClient.kt:275)
		at com.android.tools.lint.LintCliClient.run$default(LintCliClient.kt:258)
		at com.android.tools.lint.LintCliClient.analyzeOnly(LintCliClient.kt:233)
		at com.android.tools.lint.Main.run(Main.java:1761)
		at com.android.tools.lint.Main.run(Main.java:284)
		at com.android.tools.lint.Main.main(Main.java:221)

Of note is `SimplePlatformLookup$Companion.platformFromSourceProp()`,
which reads the `source.properties` files within
`$ANDROID_HOME/platforms/android-*/source.properties`.

The problem is that API-CANARY's `source.properties` contains:

	AndroidVersion.ApiLevel=36.0

which `lint` doesn't like.

`lint` has been fixed to support minor SDK versions, e.g.

  * https://cs.android.com/android-studio/platform/tools/base/+/5d246c720b206b7075a826d1bd4549882dd7c085

However, that commit was merged in 2025-May, while the latest
cmdline-tools package is 19.0, released in 2025-March.

Which means we have no `lint` package which has the fix!

As a quick workaround, *disable* lint checks in the unit tests,
by adding the block:

	android {
	  lint {
	    checkReleaseBuilds = false
	  }
	}

This *should* prevent `lint` from running at all, thus "fixing"
the `NumberFormatException`.

Co-authored-by: Jonathan Peppers <jonathan.peppers@microsoft.com>

[0]: https://developer.android.com/sdk/api_diff/b-1-beta1/changes
[1]: https://android-developers.googleblog.com/2025/08/android-16-qpr2-beta-1-is-here.html
[2]: https://developer.android.com/about/versions/16/qpr2/overview#platform-stability
[3]: https://developer.android.com/guide/topics/manifest/uses-sdk-element
[4]: https://developer.android.com/about/versions/16/features#using-new
[5]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT_FULL
[6]: https://developer.android.com/reference/android/os/Build#getMinorSdkVersion(int)
[7]: https://developer.android.com/reference/android/os/Build.VERSION_CODES_FULL#BAKLAVA_1
@github-actions github-actions bot locked and limited conversation to collaborators Oct 9, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants