Skip to content

feat(kotlin): publish a platform-independent alphaTab-core library for any JVM - #3018

Merged
Danielku15 merged 5 commits into
developfrom
feature/kotlin-core-library
Nov 8, 2026
Merged

Danielku15 merged 5 commits into
developfrom
feature/kotlin-core-library

Conversation

@Danielku15

Copy link
Copy Markdown
Member

Note

AI-authored disclosure (alphatab-ai-authored-v1)

Portions of this content were authored by an AI agent. The agent has read
AGENTS.md and the human submitter accepts responsibility for
compliance with the rules in that document.

Issues

Fixes #2458 (raised in discussion #2457)

Proposed changes

Until now, alphaTab for Kotlin was published only as the Android AAR net.alphatab:alphaTab, although nothing below the UI integration depends on Android. JVM applications such as servers and command-line tools could not use it. This PR splits the Kotlin target the way the .NET target is split (AlphaTab + AlphaTab.Windows): a portable core plus the platform integration on top. There is no Kotlin Multiplatform setup and no second full compilation of the core. alphaTab's behaviour is unchanged, apart from the one regex detail described below.

Packages

  • net.alphatab:alphaTab-core (new, JVM jar): all transpiled code, the portable hand-written runtime, and all tests. It runs on any JVM 17+ through the low-level APIs. There is no UI control, and no desktop JVM UI integration is planned.
  • net.alphatab:alphaTab (Android AAR, same coordinates as before): AlphaTabView and the Android platform code (canvas, audio, view containers, assets). It depends on the core via api, so existing Android apps need no change.

How it is built (packages/kotlin/src)

  • New Gradle module :core (Kotlin/JVM). The transpiler now writes into core/src/{main,test}-generated.
  • The tests run as plain JUnit through :core:check.
  • :android keeps only AlphaTabView.kt and platform/android/*.
  • Hand-written files moved with git mv, so their history is kept.
  • The Android module uses core internals through Kotlin friend paths, the counterpart of C#'s InternalsVisibleTo("AlphaTab.Windows"). This keeps the TS @internal surface, and with it the JS typings, unchanged.
  • Internals are not stable across versions. The AAR's Gradle module metadata therefore requires alphaTab-core with strictly <same version>, so a version mismatch fails at dependency resolution instead of on the device.
  • The shared POM, signing and version settings moved to the root build. update-version.mjs, the npm scripts and the nightly publish (root-level publishToMavenCentral) cover both artifacts.

Keeping the core Android-compatible

The core no longer compiles against android.jar. Two guards stop it from starting to use JDK APIs that Android lacks:

  • -Xjdk-release=17 restricts the code to the Java 17 class library, whichever JDK runs Gradle.
  • Animal Sniffer with the gummy-bears-api-26 signatures (the Android minSdk) runs in :core:check, which npm run test-kotlin now calls.

Two further changes protect consumers:

  • Explicit Kotlin module names (alphaTab-core, alphaTab-android) avoid clashing META-INF/*.kotlin_module files in apps.
  • alphaSkia is an api dependency of the core, because JVM applications call its native initialization (AlphaSkiaLinux.INSTANCE.initialize() etc.) themselves.

Decoupling (separate commit, also builds in the old layout)

Three hand-written runtime files depended on Android:

  • EnvironmentPartials no longer registers the android render engine or prints device details. AndroidEnvironment.initializeAndroid does both now. AlphaTabView calls it before it creates the API, so the log output is unchanged.
  • RegExp.replace always uses its own replacement loop. Before, Android 14+ used Matcher.replaceAll/replaceFirst(Function), and the JVM unit tests already ran the own loop. Behaviour change on Android 14+ only: the JDK overloads interpret $ and \ in the replacer result as group references, so a $ in a song title used in a header/footer template (e.g. "Ke$ha") was treated as one. The replacer result is now inserted literally, as in JavaScript.
  • The Java thread workers move from alphaTab.platform.android to alphaTab.platform.jvm (internal types).

Documentation (website/)

  • New page Platforms and packages: every package per ecosystem with runtime, UI control, render engines and audio output.
  • Getting Started (Android) becomes Getting Started (Kotlin):
    • installation-android is now installation-kotlin, with both packages, a JVM rendering sample and the Android setup.
    • The old URL redirects, and the old #render-your-first-score anchor still resolves.
  • The low-level API guide gets Kotlin examples next to JavaScript and C#.
  • Where "Android" meant the Kotlin API in general, it now reads "Kotlin". This covers the reference example tabs and name badges (JSDoc @example titles; doc comments only), the Skia/SVG/font pages, the feature catalog intros, the introduction, the landing texts and llms.txt. Pages about AlphaTabView, AudioTrack and the UI-bound api object keep "Android".
  • 1.10 release notes (with an upgrade note) and the contributing guide are updated.

Two website fixes outside the strict scope of #2458 that came up while editing these pages:

  • The landing page install snippet used net.alphatab:alphatab. Maven coordinates are case-sensitive, so that does not resolve; it is now alphaTab.
  • Install snippets no longer hard-code versions; the .NET page still showed 1.6.1. A small remark plugin fills {{alphaTabVersion}} and {{alphaSkiaVersion}} in code blocks with the versions the site is built for. The MIDI events guide moves from the @alpha CDN tag to @latest.

Verification

What actually ran, compared against a baseline build of develop (17793fd):

  • Tests: 2255 tests, 0 failures, both before (:android:testReleaseUnitTest) and after (:core:check). The sets of test names are identical.
  • Bytecode:
    • The class list of the core jar + AAR matches the old AAR, except the intended package move of the five worker classes.
    • The referenced JDK members are identical (178 → 176): only the two Matcher Function overloads from the RegExp change are gone.
    • The AAR contains only the 45 Android integration classes; the core jar has no android/androidx references.
  • API-26 guard: passes on the current code. A probe file using java.util.HexFormat (API 34) makes it fail, as intended.
  • Publishing: publishAllPublicationsToDistPathRepository produced both artifacts with sources and Javadoc. The AAR's POM depends on alphaTab-core in compile scope, and its module metadata contains the strictly constraint.
  • JVM consumer (scratch Gradle project, not part of this PR):
    • Depends only on alphaTab-core + alphaSkia-linux from the dist repository.
    • Loaded serenade.gp (4 tracks, 78 bars) and rendered it to PNG (alphaSkia) and SVG.
    • The documented sample and all Kotlin snippets of the low-level guide were compiled and run.
  • Android consumer (scratch app, not part of this PR):
    • minSdk 26 with R8 minification, depending on the dist AAR.
    • assembleRelease succeeds with no missing-class errors, and the APK contains the core code, Bravura.otf and the SoundFont assets.
    • Gradle resolves alphaTab-core:{strictly …}.
    • The app was built only, not run on a device or emulator.
  • CI path: npm run build-kotlin && npm run test-kotlin succeed.
  • Website: npm run build-website succeeds, including the broken-link check, after building the alphaTab dist from this branch. The rendered pages show the substituted versions, the Kotlin tabs and the redirect.

Observations outside this PR

Found while verifying; none of these are changed here:

  • The .NET console sample in installation-net creates a ScoreRenderer with lazy loading enabled and never calls renderResult, so it likely receives no partial results.
  • JVM users of the low-level API cannot use the internal canvas helper that stitches partial images, nor the internal EventEmitter when implementing ISynthOutput.
  • Apps whose Kotlin compiler is older than 2.4 cannot read alphaTab's metadata (AGP 9 defaults to Kotlin 2.2). This applies to the existing AAR too.

Checklist

  • I consent that this change becomes part of alphaTab under its current or any future open source license
  • This PR is linked to an accepted issue (see above)
  • Changes are implemented
  • New tests were added: this is a build and packaging change. The existing suite now runs in :core, the Animal Sniffer API-26 check is new, and the published artifacts were verified with the consumer projects above.
  • I have read AGENTS.md if an AI helped draft any part of this PR

AI authorship disclosure

  • No AI agent authored any part of this PR (description, code, tests, or commit messages)
  • An AI agent contributed to this PR. The AI-authored disclosure block
    (alphatab-ai-authored-v1) is present at the top of this body, and I have
    personally reviewed every change and can explain each one

Further details

  • This is a breaking change
  • Docs / release notes updated in website/

Prepares the split into a platform-independent core library (#2458):
only the Android integration may depend on Android APIs.

- EnvironmentPartials registers no Android render engine and prints no
  device details. AndroidEnvironment.initializeAndroid registers the
  "android" engine and the device lines instead; AlphaTabView calls it
  before it creates the API, so the log output is unchanged.
- RegExp uses its own replacement loop on every API level instead of
  Matcher.replaceAll/replaceFirst(Function) on Android 14+. That path is
  what all JVM unit tests already ran. Unlike the JDK overloads it
  inserts the replacer result literally (JavaScript semantics), so a
  '$' or '\' in it no longer acts as a group reference on Android 14+.
- The Java thread workers move to alphaTab.platform.jvm as they do not
  depend on Android.
The Kotlin target is split like the .NET one (AlphaTab + AlphaTab.Windows)
so alphaTab can be used on any JVM, not only on Android (#2458).

- New Gradle module :core (Kotlin/JVM) receives all transpiled code, the
  portable hand-written runtime and all tests. It is published as
  net.alphatab:alphaTab-core. Tests run as plain JUnit via :core:check.
- :android keeps AlphaTabView and the Android platform code and is still
  published as net.alphatab:alphaTab. It depends on the core via api, so
  Android apps need no change.
- The Android module reaches core internals through Kotlin friend paths,
  the counterpart of InternalsVisibleTo. Its Gradle module metadata pins
  alphaTab-core with a strict version, since internal members are not
  stable across versions.
- The core compiles against the Java 17 class library (-Xjdk-release) and
  Animal Sniffer checks it against the Android API 26 signatures, so it
  cannot start using JDK APIs that Android lacks.
- alphaSkia is an api dependency of the core: JVM applications call its
  native initialization themselves.
- Explicit Kotlin module names avoid clashing META-INF/*.kotlin_module
  files in apps.
- Shared publishing metadata and the version moved to the root build.
The Kotlin target now ships alphaTab-core for any JVM next to the Android
library (#2458). The website follows the new split:

- New page "Platforms and packages" lists every package per ecosystem
  with runtime, UI control, render engines and audio output.
- "Getting Started (Android)" becomes "Getting Started (Kotlin)":
  installation-android is now installation-kotlin with both packages, a
  verified JVM rendering sample and the Android setup; the old address
  redirects. The Android configuration page stays.
- The low-level API guide gets Kotlin examples next to JavaScript and C#.
- Where "Android" stood for the Kotlin API in general it now reads
  "Kotlin": reference example tabs and name badges (JSDoc @example
  titles), Skia/SVG/font pages, feature catalog intros, introduction,
  landing texts and llms.txt. Pages about AlphaTabView, AudioTrack and
  the UI-bound api object keep "Android".
- Release notes for 1.10 and the contributing guide describe the new
  packages and Gradle modules.
…tall snippet

Maven coordinates are case-sensitive; net.alphatab:alphatab does not
resolve, the artifact is net.alphatab:alphaTab.
…ilt for

The .NET and Kotlin install pages hard-coded package versions, which
went stale (the .NET page still showed 1.6.1). A remark plugin now
replaces {{alphaTabVersion}} and {{alphaSkiaVersion}} in code blocks and
inline code: the alphaTab version is the one the navbar and landing page
show, the alphaSkia version is read from the Kotlin version catalog that
alphaTab-core depends on. A leading backslash keeps a placeholder literal.

The MIDI events guide loads alphaTab from the @latest CDN tag like the
other pages instead of the @Alpha pre-release tag. The style guide
documents the placeholders.
@Danielku15
Danielku15 merged commit 28d0144 into develop Nov 8, 2026
12 checks passed
@Danielku15
Danielku15 deleted the feature/kotlin-core-library branch November 8, 2026 00:15
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.

[Kotlin] extract an alphatab.core library

1 participant