Skip to content

ci: add Android Java API compatibility checks - #165

Merged
soloturn merged 2 commits into
developfrom
ci/android-java-compatibility-lints
Jul 30, 2026
Merged

ci: add Android Java API compatibility checks#165
soloturn merged 2 commits into
developfrom
ci/android-java-compatibility-lints

Conversation

@BenjaminAmos

Copy link
Copy Markdown
Contributor

This pull request introduces the gradle-animalsniffer-plugin into the testing process to verify that Gestalt libraries do not call APIs which would be unavailable at runtime on Android. The API signatures are provided by the gummy-bears project.

The legacy common.gradle.kts shared logic is also moved into a more modern Gradle convention plugin, which should be easier to work with.

Further Rationale

Android is an unusual platform from a build perspective due to it having lacking support for many Java APIs. For a long time, approximately Java 8 was the only supported baseline. Java 11 support was introduced in Android 12 (2021) and Java 17 support in Android 14 (2023). We still support all the way back to Android 7 currently (my preference).

Typically, if you wanted to ensure that code still ran under an older JVM runtime, you would compile the code using -source <java_version> and -target <java_version>, with -release <java_version> replacing these two from Java 9. This ensures that the code is compatible by limiting the available classes but also has the consequence of limiting you to using solely the language features supported by that old version.

The Android build tools offers the D8 dexer as an alternative. Strictly speaking, D8 is a tool used to translate JVM bytecode into a format compatible with Android's runtime. As part of this process, it also performs a "desugaring" step, allowing features from newer Java runtimes to be backported at build time in a few cases. This breaks the assumptions of the Java compiler that language features are tied to specific runtimes and allows us to use newer language features without any of the corresponding newer APIs being available. If any of those unavailable APIs happen to be in-use, then it will likely result in an unexpected runtime failure when the class cannot be found. The plugin introduced here performs more strict checks on the classes used by our libraries in an attempt to prevent such scenarios.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Build Improvements
    • Standardized library build configuration through a shared Gradle convention.
    • Added API compatibility checks for supported platforms.
    • Improved resource handling during Java compilation.
    • Updated test module packaging and execution setup.
    • Added Android lint configuration to suppress a known warning.
    • Improved plugin resolution through local build logic.

Walkthrough

The build now uses a local gestalt-library-common convention plugin, centralizes Animal Sniffer configuration, migrates Java modules to the plugin, and adds module-specific compiler, test-task, Android lint, and signature settings.

Changes

Gradle build infrastructure

Layer / File(s) Summary
Shared convention plugin and resolution
.gitignore, build-logic/..., settings.gradle.kts
Adds local Kotlin DSL build logic, resolves the shared convention plugin through includeBuild, and configures common publishing and signature validation behavior.
Library module convention migration
gestalt-asset-core/..., gestalt-di/..., gestalt-entity-system/..., gestalt-inject..., gestalt-module/..., gestalt-util/...
Migrates modules to gestalt-library-common, retains entity-system dependencies, and passes resource directories to Java compilation.
Android signature validation
gestalt-android/build.gradle
Enables Animal Sniffer, adds the Android API signature artifact, and disables the BidiSpoofing lint check.
Specialized task and analysis configuration
gestalt-es-perf/..., gestalt-inject-java/...
Configures performance-test module gathering and excludes specialized modules from Animal Sniffer analysis where required.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: soloturn

Poem

A rabbit hops through Gradle’s maze,
With shared plugin paths ablaze.
Signatures check, resources flow,
Test modules gather as they go.
“Build logic blooms!” the bunny sings,
While lint clips troublesome things.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding Android Java API compatibility checks.
Description check ✅ Passed The description directly explains the Animalsniffer integration and Gradle convention plugin migration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/android-java-compatibility-lints

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build-logic/src/main/kotlin/gestalt-library-common.gradle.kts`:
- Around line 23-25: In the AnimalSniffer configuration for the
AnimalSnifferExtension, replace the broad java.io.File suppression with the
narrowest supported exclusion for only File.toPath(), while retaining the
java.nio.file desugaring exemption. Ensure other java.io.File APIs continue to
be validated.

In `@gestalt-android/build.gradle`:
- Around line 34-36: Remove the module-wide BidiSpoofing disable from the lint
configuration. Fix the reported issue in AndroidAssetsFileSource.java, or
replace it with a narrowly scoped supported suppression or baseline that
preserves BidiSpoofing checks for all other module sources.

In `@settings.gradle.kts`:
- Around line 19-22: Move the pluginManagement block in settings.gradle.kts
ahead of dependencyResolutionManagement, keeping its includeBuild("build-logic")
configuration unchanged so the local gestalt-library-common plugin can resolve
during settings evaluation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1a077ba9-95b6-40a5-aa8c-e72e196d61e2

📥 Commits

Reviewing files that changed from the base of the PR and between 5222e32 and f480aae.

📒 Files selected for processing (14)
  • .gitignore
  • build-logic/build.gradle.kts
  • build-logic/settings.gradle.kts
  • build-logic/src/main/kotlin/gestalt-library-common.gradle.kts
  • gestalt-android/build.gradle
  • gestalt-asset-core/build.gradle.kts
  • gestalt-di/build.gradle.kts
  • gestalt-entity-system/build.gradle.kts
  • gestalt-es-perf/build.gradle.kts
  • gestalt-inject-java/build.gradle.kts
  • gestalt-inject/build.gradle.kts
  • gestalt-module/build.gradle.kts
  • gestalt-util/build.gradle.kts
  • settings.gradle.kts

Comment on lines +23 to +25
extensions.configure<ru.vyarus.gradle.plugin.animalsniffer.AnimalSnifferExtension> {
// java.nio.* APIs can be desugared by D8. java.io.File.toPath() also needs to be excluded.
ignore = listOf("java.nio.file.*", "java.io.File")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files matching animalsniffer/build-logic =="
git ls-files | rg '(^|/)gestalt-library-common\.gradle\.kts$|animalsniffer|build-logic' || true

echo
echo "== target file =="
if [ -f build-logic/src/main/kotlin/gestalt-library-common.gradle.kts ]; then
  cat -n build-logic/src/main/kotlin/gestalt-library-common.gradle.kts
fi

echo
echo "== search ignore settings =="
rg -n "ignore\s*=|AnimalSnifferExtension|animalsniffer" build-logic/src/main/kotlin || true

echo
echo "== repository version/plugin declarations =="
rg -n "gradle-animalsniffer|animalsniffer" -S . || true

Repository: MovingBlocks/gestalt

Length of output: 5553


🌐 Web query:

gradle animalsniffer plugin AnimalSnifferExtension ignore class level suppression java.io.File

💡 Result:

The Gradle AnimalSniffer plugin (by xvik) does not provide a direct configuration option in AnimalSnifferExtension to "ignore" or suppress violations specifically for a java.io.File or any other arbitrary class type via the extension itself. Instead, Animal Sniffer handles suppression through standard mechanism provided by the underlying Animal Sniffer library [1][2]. To suppress violations at the code level, you should use the @IgnoreJRERequirement annotation [3]. This annotation is part of the animal-sniffer-annotations artifact [4]. By adding this annotation to a class or a specific method, you instruct the Animal Sniffer checker to ignore compatibility violations for the annotated code block [3]. If you need to suppress violations for a broader set of classes or files that you cannot annotate, the common practice—as seen in large-scale projects like Kotlinx Coroutines—is to use Gradle's built-in task configuration to exclude specific files from the check tasks [5]. You can achieve this by configuring the AnimalSniffer tasks in your build.gradle.kts file: tasks.withType<ru.vyarus.gradle.plugin.animalsniffer.task.AnimalSnifferTask>.configureEach { exclude("/path/to/your/class/") } This approach allows you to filter out specific files or packages from being analyzed by the plugin [5]. The AnimalSnifferExtension is primarily used to define signatures, source sets, and general plugin behavior [6][7][8], rather than granular, per-class violation management.

Citations:


🌐 Web query:

github.com xvik gradle-animalsniffer-plugin AnimalSnifferExtension ignore Groovy source

💡 Result:

The gradle-animalsniffer-plugin does not provide a direct configuration option to ignore individual source files or specific Groovy files within a source set [1][2]. The plugin operates at the level of "targets" (such as source sets, Android variants, or Kotlin multiplatform compilations) rather than granular file filtering [2][3]. To exclude specific Groovy (or other) source files from being checked by the plugin, the recommended approach is to isolate those files into a separate source set and exclude that source set from the plugin's configuration [4]. If you need to manage which source sets are checked, you can configure the plugin using the defaultTargets property [2][3]: animalsniffer { // Only check the 'main' source set, excluding others defaultTargets = ['main'] } If you have specific classes or packages that are causing issues and do not need to be checked, consider the following options: 1. Use Cache Exclusions: If you are encountering issues with specific packages (such as internal Gradle or third-party classes), you can use the animalsniffer.cache.exclude configuration [5]: animalsniffer { cache { exclude 'com.example.package.*' } } 2. Isolate Sources: As noted in the documentation for quality plugins, the cleanest way to exclude specific files is to move them into a dedicated source set and not include that source set in your defaultTargets list [4]. Note: The plugin supports Groovy source sets only when they use @CompileStatic [1][6]. If your Groovy code is not compiled statically, it may not be correctly analyzed or may require different handling. Direct "exclude file" patterns (like those found in other quality plugins) are generally not supported by the animalsniffer plugin [4][7].

Citations:


🌐 Web query:

AnimalSniffer extension ignore "ignores property" JavaSourceSetProcessor ignore signatures animal sniffer

💡 Result:

In Animal Sniffer, there are two primary ways to ignore signature violations: using the ignores configuration parameter or using source code annotations [1][2]. 1. Configuration Parameter (ignores): You can specify class names to ignore in the plugin configuration. This parameter supports wildcards [1][3]. In Maven, this is defined within the block of the animal-sniffer-maven-plugin [2]: com.example.SomeClass com.example.package.* In Gradle, if you are using the common ru.vyarus:gradle-animalsniffer-plugin, specific ignore behavior may depend on your configuration (such as cache exclusions) [4][5]. 2. Source Annotations: You can use the @IgnoreJRERequirement annotation to mark specific methods, classes, or fields that should be ignored during signature checking [6][1]. Animal Sniffer automatically recognizes this annotation by default [1]. You need to include the animal-sniffer-annotations dependency in your project (typically with scope provided or optional) to use this annotation [6]. Regarding your mention of JavaSourceSetProcessor: While this is a class name found in some build tool internal APIs, it is not a standard configuration property for Animal Sniffer. Animal Sniffer itself uses the ignores parameter [3][7] and standard annotation-based suppression [6][1] to handle exceptions to signature checks. top_results: [6][1][3][2][4][5]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== gradle plugins catalog/version catalog entries =="
for f in gradle/libs.versions.toml settings.gradle.kts build.gradle.kts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f"
  fi
done

echo
echo "== inspect AnimalSnifferExtension via source fetch if available =="
python3 - <<'PY'
import urllib.request
urls = [
    "https://raw.githubusercontent.com/xvik/gradle-animalsniffer-plugin/2.0.1/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferExtension.groovy",
    "https://raw.githubusercontent.com/xvik/gradle-animalsniffer-plugin/2.0.1/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferExtension.java",
    "https://raw.githubusercontent.com/xvik/gradle-animalsniffer-plugin/2.0.1/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/task/AnimalSnifferTask.groovy",
]
for url in urls:
    print(f"\n## {url}")
    try:
        with urllib.request.urlopen(url, timeout=20) as r:
            text = r.read().decode()
        for line in text.splitlines()[:220]:
            print(line)
    except Exception as e:
        print(f"ERROR: {e}")
PY

echo
echo "== inspect generated groovy source from maven central/gradle plugin repo if reachable =="
python3 - <<'PY'
import urllib.request, re, urllib.error
base = "https://raw.githubusercontent.com/xvik/gradle-animalsniffer-plugin/2.0.1/src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/CheckTaskHelper.groovy"
try:
    with urllib.request.urlopen(base, timeout=20) as r:
        print(r.read().decode(encoding="utf-8", errors="replace")[:8000])
except Exception as e:
    print(e)
PY

Repository: MovingBlocks/gestalt

Length of output: 4286


🌐 Web query:

site:github.com/xvik/gradle-animalsniffer-plugin src/main/groovy ru.vyarus.gradle.plugin.animalsniffer CheckTaskHelper groovy

💡 Result:

In the context of the ru.vyarus.gradle.plugin.animalsniffer plugin, there is no public API class named CheckTaskHelper documented in the official repository [1][2]. The plugin is a Gradle tool used to check API compatibility against AnimalSniffer signatures for Java, Kotlin, Groovy, and Scala projects [1]. While the plugin internally manages tasks for checking compatibility and building signatures, configuration and customization are typically performed through the animalsniffer extension and specific task types exposed by the plugin, such as ru.vyarus.gradle.plugin.animalsniffer.signature.BuildSignatureTask or ru.vyarus.gradle.plugin.animalsniffer.info.SignatureInfoTask [1][3][2]. If you encounter references to a CheckTaskHelper, it is likely an internal class within the plugin's source code not intended for public use or configuration [4]. You can manage check tasks and performance (such as enabling caching) via the animalsniffer block in your build.gradle file: animalsniffer { cache { enabled = true } } For debugging or inspecting task registration, the plugin provides utility tasks like printAnimalsnifferTasks and printAnimalsnifferSourceInfo [1][5].

Citations:


🌐 Web query:

site:github.com/xvik/gradle-animalsniffer-plugin src/main/groovy AnimalSnifferExtension groovy

💡 Result:

The file src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/AnimalSnifferExtension.groovy is a core component of the gradle-animalsniffer-plugin [1], a popular Gradle plugin developed by xvik (Vyarus) used to verify API compatibility of Java, Kotlin, Groovy, Scala, and Android projects against specific signatures (such as JDK or Android SDK versions) [2]. In the plugin's source code, AnimalSnifferExtension.groovy defines the configuration extension for the plugin [2]. This extension allows users to configure the plugin's behavior within their build.gradle or build.gradle.kts files [2]. Key details regarding the plugin and its extension include: 1. Functionality: The plugin ensures that the code being built does not accidentally use APIs that are not available in the target environment (e.g., ensuring a library meant for Java 8 doesn't use Java 11 APIs) [2]. 2. Usage with Groovy: The plugin supports Groovy projects, though it is specifically noted that Groovy projects should use @CompileStatic for optimal compatibility checking [2]. 3. Configuration: Users interact with the extension to define signatures, check configurations, and manage other plugin settings [2]. The wiki documentation provides further details on how to configure tasks and custom signatures [3]. 4. Development: The file is located in the plugin's source repository at src/main/groovy/ru/vyarus/gradle/plugin/animalsniffer/ [1]. The plugin is actively maintained, with recent versions (such as 2.0.1) released as of early 2025 [4][5]. For detailed usage, you can refer to the official repository at https://github.com/xvik/gradle-animalsniffer-plugin [2].

Citations:


🌐 Web query:

"com.toasttab.android:gummy-bears-api-24" "java.io.File" "toPath" Android signature

💡 Result:

The com.toasttab.android:gummy-bears-api-24 artifact is part of the Gummy Bears project, which provides Animal Sniffer signatures for Android APIs [1][2]. These signatures help developers ensure their code does not accidentally use APIs that are unavailable on their minimum supported Android version, while accounting for APIs made available through core library desugaring [1][3]. Regarding java.io.File.toPath, here is the context for its usage with Android and Gummy Bears: 1. Availability in Android: The java.io.File.toPath method was added to the Android platform in API level 26 [4]. It allows for obtaining a java.nio.file.Path object from a java.io.File instance [5][6]. 2. Role of Gummy Bears: Because java.io.File.toPath is not natively available on Android devices below API level 26, developers using core library desugaring may be able to use this method on older API levels [7]. Gummy Bears provides specialized Animal Sniffer signatures (such as those with the coreLib2 classifier) that include these desugared APIs [1][2]. 3. Usage in Animal Sniffer: If you are using Gummy Bears to prevent the accidental use of unavailable APIs, you would include the appropriate signature in your build configuration [1][2]. For projects utilizing core library desugaring, you would specifically reference the signature artifact that includes those desugared methods [1][2]: // Example using core library desugaring signatures dependencies { signature('com.toasttab.android:gummy-bears-api-24:0.14.0:coreLib2@signature') } Note: When using signatures with classifiers (like coreLib2), some versions of the animal-sniffer-maven-plugin may have difficulty consuming the artifact directly due to how classifiers are handled [8]. You may need to ensure your plugin configuration is set up correctly or consider the workarounds documented by the community [8].

Citations:


🌐 Web query:

Gummy Bears 0.15.0 coreLib2 java.util.zip.ZipOutputStream API level signature

💡 Result:

The Gummy Bears library (com.toasttab.android:gummy-bears) is a tool that provides Animal Sniffer signatures to validate that Android code does not use APIs unavailable on specific Android versions [1][2]. The 0.15.0 version, released on July 15, 2026, continues to provide these signatures [3][4]. Regarding the coreLib2 classifier and java.util.zip.ZipOutputStream: 1. Purpose of coreLib2: The coreLib2 classifier in Gummy Bears provides experimental sets of signatures for APIs made available to older Android versions via Android's core library desugaring (desugar_jdk_libs version 2.1.5 or above) [1][2]. These are used to ensure that libraries are compatible with projects that enable this desugaring at build time [1]. 2. java.util.zip.ZipOutputStream API level: - The java.util.zip.ZipOutputStream class has been part of the Android SDK since API level 1 [5]. - Consequently, basic constructors and methods for ZipOutputStream are available from API level 1 [5]. - If you are specifically looking for signatures related to ZipOutputStream within Gummy Bears, note that because it is a standard Java API available since the beginning of Android, it does not typically require "desugaring" (which is used for bringing newer Java APIs like java.time to older Android versions) [1][5]. If you are using the coreLib2 classifier to validate your project, it is designed to account for these backported/desugared APIs [1]. Since java.util.zip.ZipOutputStream is not a desugared API in the same category as java.time or ConcurrentHashMap, its standard usage is governed by the base Android API level signatures rather than the coreLib2 desugared signatures [1][5].

Citations:


🌐 Web query:

"gummy-bears-api-24" "java.io.File" "open" "newFile" signature

💡 Result:

The identifier gummy-bears-api-24 refers to a library provided by open-toast that supplies Animal Sniffer signatures for Android API level 24 [1][2]. These signatures are used to ensure code compatibility by detecting the use of APIs that are not available on specific Android versions, taking into account desugared APIs [1][3]. There is no "open newFile" method or signature in the java.io.File class. The java.io.File class in the standard Java API (JDK) provides the following constructor signatures for creating a new File instance [4][5]: public File(String pathname) public File(String parent, String child) public File(File parent, String child) public File(URI uri) The gummy-bears library does not modify the standard Java File class or its constructors; it merely provides signature files (based on the Animal Sniffer format) used to validate your code against the Android SDK's surface area [1][2]. If you are encountering an error related to java.io.File while using gummy-bears, it is likely due to your code attempting to use a constructor or method signature that does not exist in the Java API or is not permitted by the API level you are targeting [1][3].

Citations:


Exclude only File.toPath() from AnimalSniffer.

ignore = listOf("java.io.File") suppresses validation across the entire java.io.File API, not just the File.toPath() call referenced by the comment. Confine the exception to the Android-facing code path, or target the narrowest supported suppression mechanism so future incompatible File APIs remain caught.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build-logic/src/main/kotlin/gestalt-library-common.gradle.kts` around lines
23 - 25, In the AnimalSniffer configuration for the AnimalSnifferExtension,
replace the broad java.io.File suppression with the narrowest supported
exclusion for only File.toPath(), while retaining the java.nio.file desugaring
exemption. Ensure other java.io.File APIs continue to be validated.

Comment on lines +34 to +36
lint {
// Somehow, AndroidAssetsFileSource.java breaks this lint.
disable "BidiSpoofing"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)build\.gradle$|AndroidAssetsFileSource\.java$' || true

echo "== gestalt-android/build.gradle context =="
if [ -f gestalt-android/build.gradle ]; then
  nl -ba gestalt-android/build.gradle | sed -n '1,90p'
  echo "== BidiSpoofing occurrences =="
  rg -n 'BidiSpoofing|lint|disable|baseline|SuppressWarnings' gestalt-android/build.gradle gestalt-android || true
fi

echo "== AndroidAssetsFileSource.java context =="
if [ -f gestalt-android/AndroidAssetsFileSource.java ]; then
  nl -ba gestalt-android/AndroidAssetsFileSource.java | sed -n '1,220p'
fi

echo "== repo-wide occurrences =="
rg -n 'BidiSpoofing|AndroidAssetsFileSource|class AndroidAssetsFileSource' . || true

Repository: MovingBlocks/gestalt

Length of output: 594


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, re, pathlib
# Read-only behavioral/deterministic parser-like probe: locate BidiSpoofing disable and any narrower suppression references in relevant files.
files = ["gestalt-android/build.gradle", "gestalt-android/AndroidAssetsFileSource.java"]
for f in files:
    p = pathlib.Path(f)
    if not p.exists():
        print(f"MISSING {f}")
        continue
    s = p.read_text()
    print(f"\n== {f} ==")
    for i, line in enumerate(s.splitlines(), 1):
        if 'BidiSpoofing' in line or '`@SuppressLint`' in line or 'lintOptions' in line or 'disable ' in line:
            print(f"{i}: {line.strip()}")
PY

Repository: MovingBlocks/gestalt

Length of output: 292


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== gestalt-android/build.gradle =="
awk '{print NR":"$0}' gestalt-android/build.gradle | sed -n '1,120p'

echo "== gestalt-android/src/main/java/org/terasology/gestalt/android/AndroidAssetsFileSource.java =="
awk '{print NR":"$0}' gestalt-android/src/main/java/org/terasology/gestalt/android/AndroidAssetsFileSource.java | sed -n '1,260p'

echo "== relevant repo occurrences =="
rg -n 'BidiSpoofing|AndroidAssetsFileSource|`@SuppressLint`|lintOptions|baseline.*Bidi|AndroidAssetsFileSource' .

echo "== android gradle plugins and versions =="
rg -n 'com\.android\.(library|application)|android\.lint|lintOptions' -g '*.gradle' .

Repository: MovingBlocks/gestalt

Length of output: 12244


Do not disable BidiSpoofing for the entire module.

BidiSpoofing guards against misleading bidirectional Unicode strings; disabling it in lint { disable "BidiSpoofing" } removes that security check from all module sources. Fix the reported case in gestalt-android/src/main/java/org/terasology/gestalt/android/AndroidAssetsFileSource.java or use a narrower supported suppression/baseline instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@gestalt-android/build.gradle` around lines 34 - 36, Remove the module-wide
BidiSpoofing disable from the lint configuration. Fix the reported issue in
AndroidAssetsFileSource.java, or replace it with a narrowly scoped supported
suppression or baseline that preserves BidiSpoofing checks for all other module
sources.

Comment thread settings.gradle.kts
Comment on lines +19 to +22
pluginManagement {
includeBuild("build-logic")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files:\n'
git ls-files | sed -n '1,120p'

printf '\nsettings.gradle.kts:\n'
cat -n settings.gradle.kts 2>/dev/null || true

printf '\nGradle wrapper/version files:\n'
fd -a 'gradle/wrapper|gradle.properties|gradlew' . | sed -n '1,80p'

printf '\nBuild logic presence:\n'
test -d build-logic && find build-logic -maxdepth 3 -type f | sort || true

Repository: MovingBlocks/gestalt

Length of output: 11027


🌐 Web query:

Gradle documentation settings file pluginManagement must be first block dependencyResolutionManagement order

💡 Result:

In a Gradle settings file (settings.gradle or settings.gradle.kts), the pluginManagement block must be the first block in the file [1][2]. This requirement exists because Gradle must compile and evaluate pluginManagement before the rest of the settings script can be processed, as it dictates how plugins used in the build are resolved [3]. In contrast, the dependencyResolutionManagement block does not have the same strict requirement to be the first block [4][5]. It is typically placed after pluginManagement and other initial settings, following standard Gradle documentation examples, but it is not technically constrained to be the first block like pluginManagement [4][6][7]. In practice, both are commonly declared in the settings file to centralize repositories and version management for the entire build [5][8]. A standard order found in official documentation is [4][6][7]: 1. pluginManagement {... } 2. plugins {... } 3. rootProject.name = "..." 4. dependencyResolutionManagement {... } 5. include(...) This structure ensures that plugins are configured first, followed by project-wide dependency resolution strategies [4][5].

Citations:


Move pluginManagement before dependencyResolutionManagement.

Gradle requires pluginManagement to be the first settings block so plugins can be resolved during settings evaluation. With the current ordering, the local gestalt-library-common plugin resolution may fail.

Proposed fix
+pluginManagement {
+    includeBuild("build-logic")
+}
+
 dependencyResolutionManagement {
     // ...
 }
 
-pluginManagement {
-    includeBuild("build-logic")
-}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@settings.gradle.kts` around lines 19 - 22, Move the pluginManagement block in
settings.gradle.kts ahead of dependencyResolutionManagement, keeping its
includeBuild("build-logic") configuration unchanged so the local
gestalt-library-common plugin can resolve during settings evaluation.

@Cervator

Copy link
Copy Markdown
Member

This rings a bell, but a very vague one since it has been years since I looked at this last - but I do remember "cheaty" ways to go use some newer language features without breaking backwards compatibility. It is exciting to see that again! And once more make me think about Terasology maybe eventually making it to Android, one day :-)

Changes look fine to me, but I suppose the big question here is testing everything. That might take just a bit due to the extra steps for the setup. I think I've done it before and may even have Android Studio set up and working around her somewhere. Plus: old Android devices!

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Android Java API-compatibility verification using the Gradle AnimalSniffer plugin (with gummy-bears signatures) and migrates shared Gradle build logic from an apply(from=...) script to a Gradle convention plugin via an included build-logic composite build.

Changes:

  • Introduce build-logic composite build and a gestalt-library-common convention plugin applied across library modules.
  • Enable AnimalSniffer checks against API 24 signatures and selectively exclude checks for non-Android-targeted modules.
  • Add AnimalSniffer (and a lint suppression) to the Android library build.

Reviewed changes

Copilot reviewed 12 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
settings.gradle.kts Includes the build-logic composite build so convention plugins can be resolved.
gestalt-util/build.gradle.kts Switches from legacy shared script to the gestalt-library-common convention plugin.
gestalt-module/build.gradle.kts Switches from legacy shared script to the gestalt-library-common convention plugin.
gestalt-inject/build.gradle.kts Switches from legacy shared script to the gestalt-library-common convention plugin.
gestalt-inject-java/build.gradle.kts Switches to convention plugin and excludes AnimalSniffer tasks for this compile-time-only artifact.
gestalt-es-perf/build.gradle.kts Switches to convention plugin, modernizes task registration, and excludes AnimalSniffer tasks for this non-Android library.
gestalt-entity-system/build.gradle.kts Switches from legacy shared script to the gestalt-library-common convention plugin.
gestalt-di/build.gradle.kts Switches from legacy shared script to the gestalt-library-common convention plugin.
gestalt-asset-core/build.gradle.kts Switches from legacy shared script to the gestalt-library-common convention plugin.
gestalt-android/build.gradle Adds AnimalSniffer + API 24 signature, and adds a lint suppression plus task configuration.
build-logic/src/main/kotlin/gestalt-library-common.gradle.kts Defines the shared Java/publishing/test configuration and applies/configures AnimalSniffer signatures/ignores.
build-logic/build.gradle.kts Adds the included-build buildscript for compiling the convention plugin (kotlin-dsl + plugin dependency).
build-logic/settings.gradle.kts Settings file for the included build-logic build (currently empty).
.gitignore Ignores the included build’s .gradle directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
}

tasks.withType(AnimalSniffer) {
Comment on lines +34 to +37
lint {
// Somehow, AndroidAssetsFileSource.java breaks this lint.
disable "BidiSpoofing"
}
@soloturn
soloturn merged commit 7bc8901 into develop Jul 30, 2026
8 checks passed
@BenjaminAmos
BenjaminAmos deleted the ci/android-java-compatibility-lints branch July 30, 2026 21:28
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.

4 participants