diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b439c..a661edc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,16 @@ versions follow [Semantic Versioning](https://semver.org/). ## [Unreleased] +- **Build toolchain and libraries updated.** No user-visible change intended. + - Gradle 8.13 → 9.8.0, Android Gradle plugin 8.13.0 → 9.4.1 (built-in + Kotlin: the `org.jetbrains.kotlin.android` plugin is gone), Kotlin + 2.0.21 → 2.4.21, KSP → 2.3.12, Compose BOM 2024.12.01 → 2026.09.00. + - core-ktx 1.19.1, navigation-compose 2.10.2, Room 2.8.5, + hilt-navigation-compose 1.4.0, appcompat 1.8.0, Hilt 2.60.1, + kotlinx-coroutines 1.11.0, kotlinx-serialization 1.11.0, Robolectric 4.17. + - `compileSdk` 36 → 37 (required by the new AndroidX libraries); + `targetSdk` stays 36, `minSdk` stays 24. + ## [2.1.1] - 2026-10-12 - **In-app updates from GitHub Releases.** The app checks this repository's diff --git a/CLAUDE.md b/CLAUDE.md index 49acf5c..6c89d88 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -41,7 +41,7 @@ and **not** a damage calculator. | Gradle root project | `CoverDex` | | `applicationId` / package | `com.marcogn.coverdex` | | Database file | `coverdex.db` | -| minSdk / targetSdk / compileSdk | 24 / 36 / 36 | +| minSdk / targetSdk / compileSdk | 24 / 36 / 37 | CoverDex is a rewrite of a working app, not a new one. It used to ship as a React/Capacitor PWA on GitHub Pages; that path is gone (see @@ -251,7 +251,8 @@ there. - **Never `clearAllTables()`** — cache invalidation must name the cache tables explicitly or it takes the user's teams and roster with it. - **Every Robolectric test class needs `@Config(sdk = [26])`.** Robolectric's - shadow jar for this app's `compileSdk` (36) requires a newer JDK than CI + shadow jar for the SDK level it picks by default (this app's `targetSdk`, + 36) requires a newer JDK than CI runs (`android-ci.yml` pins JDK 17) — without the pin, the test passes locally on a machine with a newer JDK and fails in CI with a confusing `UnsupportedOperationException at DefaultSdkProvider.java` that doesn't @@ -282,11 +283,17 @@ there. `AssetManager.open()` call, whose Robolectric shadow is backed by whatever directory AGP's generated `test_config.properties` names as `android_merged_assets` — which is the actual `debug` variant's - `mergeDebugAssets` output, not a `debugUnitTest`-specific one (this AGP - version has no such task). `app/build.gradle.kts` wires the schemas into + `mergeDebugAssets` output, not a `debugUnitTest`-specific one (AGP 8.13 + and 9.4.1 have no such task). `app/build.gradle.kts` wires the schemas into `sourceSets["debug"].assets` instead, which is what actually makes `Migration1To2Test` pass. See `docs/implementation-decisions.md`, "Phase 2", for how this was verified rather than assumed. +- **A ViewModel built over a test's Room database must be cleared before + `@After` closes it.** Its `viewModelScope` otherwise keeps collecting a + Room flow, which can re-query after `database.close()`; Room 2.8 fails that + with "connection is closed", reported against whichever test is running + (an intermittent failure). Create test ViewModels through + `TrackedViewModels` (test sources) and call its `clear()` first in `@After`. - **`ALTER TABLE ... ADD COLUMN` needs a `DEFAULT` when the column is `NOT NULL`.** SQLite (and therefore Room's own migration SQL) rejects a `NOT NULL` column added this way with no default — every pre-existing diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 47cd9d4..e0b57cc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.ksp) alias(libs.plugins.kotlin.serialization) @@ -9,7 +8,9 @@ plugins { android { namespace = "com.marcogn.coverdex" - compileSdk = 36 + // 37 because core 1.19, navigation 2.10, hilt-navigation-compose 1.4 and lifecycle 2.11 + // require it (their AAR metadata); targetSdk stays 36. + compileSdk = 37 defaultConfig { applicationId = "com.marcogn.coverdex" @@ -78,10 +79,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } - buildFeatures { compose = true buildConfig = true @@ -104,7 +101,7 @@ android { // backed by the real `debug` variant's merged assets directory on disk, not by the JVM test // classpath. Adding the schemas to sourceSets["test"].assets (the officially documented // pattern for instrumented androidTest) does nothing for a Robolectric-based unit test in - // this AGP version — there is no separate assets merge for the debugUnitTest variant, so the + // AGP 8.13 or 9.4.1 (both checked) — there is no separate assets merge for the debugUnitTest variant, so the // only directory Robolectric's AssetManager ever sees is `debug`'s own mergeDebugAssets // output. So the schemas must be wired in as a `debug` source set asset instead; the handful // of KB this adds to debug-only APKs is an accepted cost. Without this, Migration1To2Test @@ -112,11 +109,19 @@ android { // docs/implementation-decisions.md, "Phase 2". sourceSets { getByName("debug") { - assets.srcDirs("$projectDir/schemas") + assets.directories.add("$projectDir/schemas") } } } +// AGP 9 compiles Kotlin itself (built-in Kotlin), so the org.jetbrains.kotlin.android plugin and +// its android { kotlinOptions } block are gone; the compiler options live here instead. +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } +} + ksp { arg("room.schemaLocation", "$projectDir/schemas") } diff --git a/app/src/test/java/com/marcogn/coverdex/TrackedViewModels.kt b/app/src/test/java/com/marcogn/coverdex/TrackedViewModels.kt new file mode 100644 index 0000000..30e9c92 --- /dev/null +++ b/app/src/test/java/com/marcogn/coverdex/TrackedViewModels.kt @@ -0,0 +1,27 @@ +package com.marcogn.coverdex + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStore + +/** + * Holds the ViewModels a test creates in a real [ViewModelStore], so [clear] cancels their + * `viewModelScope` the way a device does when the screen goes away. A test that owns a Room + * database calls [clear] before `database.close()`: otherwise a ViewModel's still-collecting Room + * flow can re-query on Room's executor after the close, which Room 2.8 fails with + * "connection is closed", reported against whichever test is running. + */ +class TrackedViewModels { + private val store = ViewModelStore() + private var created = 0 + + fun add(viewModelClass: Class, create: () -> T): T { + val factory = object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): VM = create() as VM + } + return ViewModelProvider(store, factory)["viewModel${created++}", viewModelClass] + } + + fun clear() = store.clear() +} diff --git a/app/src/test/java/com/marcogn/coverdex/ui/surprise/SurpriseMeViewModelTest.kt b/app/src/test/java/com/marcogn/coverdex/ui/surprise/SurpriseMeViewModelTest.kt index 650c305..a209db8 100644 --- a/app/src/test/java/com/marcogn/coverdex/ui/surprise/SurpriseMeViewModelTest.kt +++ b/app/src/test/java/com/marcogn/coverdex/ui/surprise/SurpriseMeViewModelTest.kt @@ -3,6 +3,7 @@ package com.marcogn.coverdex.ui.surprise import androidx.room.Room import androidx.test.core.app.ApplicationProvider import com.marcogn.coverdex.MainDispatcherRule +import com.marcogn.coverdex.TrackedViewModels import com.marcogn.coverdex.data.local.CoverDexDatabase import com.marcogn.coverdex.data.repository.CustomPokemonRepositoryImpl import com.marcogn.coverdex.data.repository.TeamRepositoryImpl @@ -51,14 +52,19 @@ class SurpriseMeViewModelTest { @After fun closeDatabase() { + viewModels.clear() database.close() } - private fun viewModel() = SurpriseMeViewModel( - pokedexRepository = FakePokedexRepository(chart, pool = pool), - customPokemonRepository = customPokemonRepository, - teamRepository = teamRepository, - ) + private val viewModels = TrackedViewModels() + + private fun viewModel() = viewModels.add(SurpriseMeViewModel::class.java) { + SurpriseMeViewModel( + pokedexRepository = FakePokedexRepository(chart, pool = pool), + customPokemonRepository = customPokemonRepository, + teamRepository = teamRepository, + ) + } @Test fun `locking and removing an anchor updates lockedMembers`() = runTest(mainDispatcherRule.dispatcher) { diff --git a/app/src/test/java/com/marcogn/coverdex/ui/team/analysis/AnalysisViewModelTest.kt b/app/src/test/java/com/marcogn/coverdex/ui/team/analysis/AnalysisViewModelTest.kt index 761f468..460dc0c 100644 --- a/app/src/test/java/com/marcogn/coverdex/ui/team/analysis/AnalysisViewModelTest.kt +++ b/app/src/test/java/com/marcogn/coverdex/ui/team/analysis/AnalysisViewModelTest.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.SavedStateHandle import androidx.room.Room import androidx.test.core.app.ApplicationProvider import com.marcogn.coverdex.MainDispatcherRule +import com.marcogn.coverdex.TrackedViewModels import com.marcogn.coverdex.data.local.CoverDexDatabase import com.marcogn.coverdex.data.repository.CustomPokemonRepositoryImpl import com.marcogn.coverdex.data.repository.TeamRepositoryImpl @@ -59,16 +60,22 @@ class AnalysisViewModelTest { @After fun closeDatabase() { + viewModels.clear() database.close() } - private fun viewModel(teamId: String, pool: List = emptyList()) = AnalysisViewModel( - savedStateHandle = SavedStateHandle(mapOf("teamId" to teamId)), - teamRepository = teamRepository, - pokedexRepository = FakePokedexRepository(chart, pool = pool), - customPokemonRepository = customPokemonRepository, - settingsPreferences = settingsPreferences, - ) + private val viewModels = TrackedViewModels() + + private fun viewModel(teamId: String, pool: List = emptyList()) = + viewModels.add(AnalysisViewModel::class.java) { + AnalysisViewModel( + savedStateHandle = SavedStateHandle(mapOf("teamId" to teamId)), + teamRepository = teamRepository, + pokedexRepository = FakePokedexRepository(chart, pool = pool), + customPokemonRepository = customPokemonRepository, + settingsPreferences = settingsPreferences, + ) + } private fun waterFlyingWithElectricMove(): TeamMember { val thunderbolt = PokemonMove( diff --git a/build.gradle.kts b/build.gradle.kts index ba84a72..ffb6919 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.kotlin.ksp) apply false alias(libs.plugins.kotlin.serialization) apply false diff --git a/docs/implementation-decisions.md b/docs/implementation-decisions.md index 04ee73b..dfcb0ff 100644 --- a/docs/implementation-decisions.md +++ b/docs/implementation-decisions.md @@ -1141,3 +1141,39 @@ and `branch-name.sh` hooks, the `self-review`, `deps-triage` and `issues-triage` and gains `actions: write` (the kit deletes old runs after a successful release); the `ANDROID_*` → `RELEASE_*` secret mapping stays. `CLAUDE.md` got the tooling list, the review-before-PR rule and the session/branch naming rule. Left out: nothing. Brought to 1721624 in the same PR (only `branch-name.sh`, rewritten to deny only clear cases, and one line of `docs/claude.md`). + +## Dependabot #44, #46 and #52 combined (2026-10-18) + +The three open Dependabot PRs could not land one at a time, each red in CI for a reason the next +one fixes: AGP 9.4.1 (#44) needs Gradle ≥ 9.6.0; Gradle 9.8.0 (#46) removed an internal API that +AGP 8.13 uses; Hilt ≥ 2.59 (#46) requires AGP 9 for its Gradle plugin; and core 1.19, +navigation 2.10, hilt-navigation-compose 1.4 and (transitively) lifecycle 2.11 (#52) declare AGP +≥ 9.1 and `compileSdk` ≥ 37 in their AAR metadata. So they land together, in one PR. + +- **Built-in Kotlin.** AGP 9 compiles Kotlin itself and the `org.jetbrains.kotlin.android` plugin is + not compatible with its new DSL ([AGP 9.0 release + notes](https://developer.android.com/build/releases/agp-9-0-0-release-notes)). The plugin is + removed (catalogue, root and app build scripts); `android { kotlinOptions }` becomes a top-level + `kotlin { compilerOptions }` block. No opt-out properties (`android.builtInKotlin=false`, + `android.newDsl=false`): the release notes say they go away in AGP 10. +- **`compileSdk` 37, `targetSdk` 36.** Only compiling against 37 is required; moving `targetSdk` + opts into new runtime behaviour and is a separate decision. +- **Room schemas on the `debug` assets** now use `assets.directories.add(...)`: `srcDirs(...)` is + deprecated in AGP 9's DSL. Same directory, same reason (see "Phase 2"). +- **Gradle wrapper** regenerated with `./gradlew wrapper --gradle-version 9.8.0 + --gradle-distribution-sha256-sum …` (the jar is byte-identical to Dependabot's), which also adds + `distributionSha256Sum`, so a corrupted or tampered distribution download fails instead of running. +- **`lifecycle` 2.8.7 → 2.11.0** in the catalogue: navigation 2.10 and hilt-navigation-compose 1.4 + already resolve it to 2.11.0, so the catalogue now states what ships. +- **ViewModel tests clear their ViewModels before closing the database.** With Room 2.8, + `AnalysisViewModelTest` failed intermittently with "connection is closed": the ViewModels it + built were never cleared, so their `viewModelScope` Room flows re-queried after `@After`'s + `database.close()` (one run passed, the next failed). `TrackedViewModels` (test sources) holds + them in a real `ViewModelStore`; `clear()` cancels their scopes as a device does, and + `AnalysisViewModelTest` and `SurpriseMeViewModelTest` (the only ViewModel tests over Room) + call it before closing. A query already running when the scope is cancelled ends inside a + cancelled coroutine, where its exception is not reported. `mergeDebugUnitTestAssets` is still + absent on AGP 9.4.1 (re-checked), so the "Phase 2" schema-assets wiring stands unchanged. +- **Left as warnings, for a follow-up**: `androidx.hilt.navigation.compose.hiltViewModel` (moved to + `androidx.hilt.lifecycle.viewmodel.compose` in hilt 1.3) and `MenuAnchorType` (Material 3 1.4's + `ExposedDropdownMenuAnchorType`). Deprecations only; the build has no warnings-as-errors. diff --git a/docs/plan/README.md b/docs/plan/README.md index 5c729a8..1de200b 100644 --- a/docs/plan/README.md +++ b/docs/plan/README.md @@ -136,7 +136,7 @@ network was reachable but `ANDROID_HOME` was unset and no SDK was installed. Check, in this order, and do not burn time fighting it: ```bash -curl -sfo /dev/null -w '%{http_code}\n' https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/8.13.0/gradle-8.13.0.pom +curl -sfo /dev/null -w '%{http_code}\n' https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/9.4.1/gradle-9.4.1.pom echo "$ANDROID_HOME"; command -v sdkmanager ``` diff --git a/docs/plan/native-spec.md b/docs/plan/native-spec.md index 635790b..9d9acec 100644 --- a/docs/plan/native-spec.md +++ b/docs/plan/native-spec.md @@ -17,7 +17,7 @@ agent does not have to reverse-engineer intent from that code. | `applicationId` / package | `com.marcogn.coverdex` | | App label | CoverDex | | Database file | `coverdex.db` | -| minSdk / targetSdk / compileSdk | 24 / 36 / 36 | +| minSdk / targetSdk / compileSdk | 24 / 36 / 37 | `applicationId` is **unchanged** from the Capacitor build — the app keeps its identity on any device that already has it installed. `minSdk` stays at 24 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 89488b1..5bf5161 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,24 +1,24 @@ [versions] -agp = "8.13.0" -kotlin = "2.0.21" -ksp = "2.0.21-1.0.28" -coreKtx = "1.13.1" -lifecycle = "2.8.7" +agp = "9.4.1" +kotlin = "2.4.21" +ksp = "2.3.12" +coreKtx = "1.19.1" +lifecycle = "2.11.0" activityCompose = "1.9.3" -composeBom = "2024.12.01" -navigationCompose = "2.8.5" -room = "2.6.1" -hilt = "2.52" -hiltNavigationCompose = "1.2.0" -kotlinxSerializationJson = "1.7.3" -kotlinxCoroutines = "1.9.0" +composeBom = "2026.09.00" +navigationCompose = "2.10.2" +room = "2.8.5" +hilt = "2.60.1" +hiltNavigationCompose = "1.4.0" +kotlinxSerializationJson = "1.11.0" +kotlinxCoroutines = "1.11.0" coil = "2.7.0" junit = "4.13.2" androidxTestExtJunit = "1.3.0" espresso = "3.6.1" -robolectric = "4.16.1" +robolectric = "4.17" datastorePreferences = "1.1.1" -appcompat = "1.7.0" +appcompat = "1.8.0" desugarJdkLibs = "2.1.5" [libraries] @@ -58,7 +58,6 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlin-ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55..5097068 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f853b..1eca32e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,10 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionSha256Sum=bafd5ce9cfaea0fbccfdc8439a1ac42fbd4cd9c89dc9a988228d8a2639a58e6c +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 23d15a9..249efbb 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index 5eed7ee..03754c4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -19,12 +19,39 @@ @if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem -@rem Gradle startup script for Windows +@rem gradlew startup script for Windows @rem @rem ########################################################################## -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +@rem Catch executions from older scripts and ensure they exit cleanly. +@rem This can be removed once we can be reasonably confident that few people +@rem will be migrating directly to this new wrapper. +goto afterSafetyNet +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: +goto exitWithErrorLevel +:afterSafetyNet set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. @@ -45,13 +72,14 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. -goto fail +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% @@ -59,36 +87,26 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +1>&2 echo. +1>&2 echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +1>&2 echo. +1>&2 echo Please set the JAVA_HOME variable in your environment to match the +1>&2 echo location of your Java installation. -goto fail +"%COMSPEC%" /c exit 1 +goto exitWithErrorLevel :execute @rem Setup the command line -set CLASSPATH= -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel & goto exitWithErrorLevel -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem This label must not be changed. We rely on old scripts being able to jump to this point. +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%