diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae75894..ffadf2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 with: - java-version: "21" + java-version: "26" distribution: "temurin" - uses: gradle/actions/setup-gradle@v4 - run: ./gradlew build diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index de04119..a4f7c24 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -23,7 +23,7 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: "21" + java-version: "26" distribution: "temurin" - uses: gradle/actions/setup-gradle@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b294f3e..67392cc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-java@v4 with: - java-version: "21" + java-version: "26" distribution: "temurin" - uses: gradle/actions/setup-gradle@v4 diff --git a/.java-version b/.java-version new file mode 100644 index 0000000..978b4e8 --- /dev/null +++ b/.java-version @@ -0,0 +1 @@ +26 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 442d3bf..480a92f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -FROM eclipse-temurin:21-jdk-alpine AS builder +FROM eclipse-temurin:26-jdk-alpine AS builder WORKDIR /build COPY gradle gradle COPY gradlew settings.gradle build.gradle ./ COPY src src RUN ./gradlew jar --stacktrace -FROM eclipse-temurin:21-jre-alpine +FROM eclipse-temurin:26-jre-alpine RUN addgroup -S bot && adduser -S bot -G bot WORKDIR /app COPY --from=builder /build/build/libs/discord-bot-1.0.0.jar app.jar @@ -18,4 +18,4 @@ EXPOSE 8712 HEALTHCHECK --interval=30s --timeout=5s --start-period=15s \ CMD wget --no-verbose --tries=1 --spider http://localhost:8712/health || exit 1 ENV DB_PATH=/app/data/bot.db -ENTRYPOINT ["java", "-jar", "app.jar"] +ENTRYPOINT ["java", "-Xmx512m", "-XX:+UseG1GC", "-XX:+UseStringDeduplication", "-jar", "app.jar"] diff --git a/TODO.md b/TODO.md index e3a3cce..15c30f8 100644 --- a/TODO.md +++ b/TODO.md @@ -257,6 +257,94 @@ feature/economy/ --- +### 🐾 Животные — зоо-викторины и картинки (public-apis) — 🟢 в работе + +- [x] `feature/animals/AnimalApi` — клиент к 8 API (thecatapi, dog.ceo, randomfox, shibe.online, some-random-api, nekos.life, waifu.pics, catboys.com) с fallback-цепочкой +- [x] `!catperson` / `!dogperson` / `!foxperson` — «Какой ты кот/пёс/лис»: % + роль + картинка (детерминировано ID) +- [x] `!shibe` / `!birb` — картинки шиба-ину и птичек +- [x] `!neko` / `!waifu` / `!catboy` — аниме-картинки (неко, вайфу, кошко-мальчик) +- [ ] Факты: `!catfact` / `!dogfact` — факт + картинка (catfact.ninja, dog facts API) +- [ ] `!joke` — анекдоты (jokeapi.dev, категории Any/Programming/Misc) +- [ ] `!chuck` — цитаты Чака Норриса (api.chucknorris.io) +- [ ] `!inspire` — мотивационные цитаты (zenquotes.io) +- [ ] `!apod` — астрономическая картинка дня NASA (api.nasa.gov, бесплатный ключ) +- [ ] `!iss` — где сейчас МКС (api.wheretheiss.at) +- [ ] `!space` — кто сейчас в космосе (open-notify.org) +- [ ] `!xkcd` — свежий стрип (xkcd.com/api) +- [ ] `!meme ` — расширить мем по сабреддиту + +--- + +## ⚡ Стабильность и скорость (2026-08-19, аудит сервера 185.176.94.38) + +### Найдено в логах +| # | Проблема | Статус | +|---|----------|--------| +| S01 | `LogEmbeds.ban`/`kick` — NPE `Value may not be null`, если бан/кик без причины (падал onSuccess-колбэк, терялся лог) | ✅ `reason != null ? reason : "Не указана"` | +| S02 | `RatingService.getDeterministicValue` — гонка на общем `Random.setSeed()`: 2 пользователя одновременно → результаты «подменялись» | ✅ локальный `new Random(seed)` (thread-safe) | +| S03 | Пул репортов на внешние API отсутствовал | ✅ `util/ApiRequestQueue` (пул 4, изоляция ответов) | +| S04 | `Missed 2 heartbeats` — 1 раз, транзиентный (сеть), не повторяется | ➖ наблюдать | + +### Что сделано +- `util/ApiRequestQueue` — очередь исходящих запросов: каждый запрос выполняется изолированно, ответ не может подмешаться к соседнему, пул 4 не перегружает публичные API. Подключён в `AnimalApi` и `NekosLifeApi`, зарегистрирован в LifecycleModule (graceful shutdown) +- `RatingService` — детерминированное значение теперь без общего `Random` (root cause «подмены ответов») +- `LogEmbeds.ban/kick` — дефолт причины «Не указана» + +### Производительность (наблюдения) +- Команды выполняются за 0–100 мс (virtual threads), CPU ~0.2%, RAM 186 МБ — узких мест нет +- Основная задержка — внешние HTTP API (таймауты 10 с + retry с бэк-оффом); очередь ограничивает параллелизм до 4 + +### Оптимизации (2026-08-19) +- [x] `AnimalApi` — Caffeine-кэш успешных URL (TTL 30 с): повторные `!neko`/`!catboy`/зоо-викторины отвечают мгновенно +- [x] `MemeCommand` — HttpURLConnection (без таймаутов, мог висеть) → OkHttp 6с/8с +- [ ] `Database` — включить `PRAGMA journal_mode = WAL` (сейчас нет — блокировки при конкурентной записи) +- [ ] NekosLifeApi — таймауты/ретраи вынести в конфиг (10с → 8с, retry 3 → 2) +- [ ] Негативный кэш неудачных запросов в AnimalApi (не долбить упавший API 30с) +- [ ] Dockerfile — JVM флаги (`-Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication`) +- [ ] Разобрать остальные `HttpURLConnection` на общий OkHttp-клиент + +--- + +## 🚧 v2.5 — Java 25, рейтинги, память (2026-08-19) + +### 1. Java 26 + Gradle 9.7.0 +- [x] `build.gradle`: `toolchain { languageVersion = 26 }` — Gradle сам находит JDK 26 (`/usr/lib/jvm/java-26-openjdk`), `export JAVA_HOME` больше не нужен +- [x] `.java-version` (файл `26`) — для IDE/jenv/asdf +- [x] `gradle/wrapper/gradle-wrapper.properties`: 9.3.1 → 9.7.0 (системный gradle 9.7.0 в `/usr/bin/gradle`) +- [x] `Dockerfile`: `eclipse-temurin:21-jdk-alpine` → `26-jdk-alpine`, `21-jre-alpine` → `26-jre-alpine` +- [x] Mockito 5.15.2 тянет ByteBuddy 1.14.x (не знает класс-файлы major 70) → добавить `byte-buddy:1.18.8` (уже в кэше, поддержка Java 26) +- [x] CI workflows (ci/docker-publish/release): `java-version: "21"` → `"26"` +- [x] Пересобрать локально + `spotlessApply` (Spotless 6.25→8.10.0, SpotBugs 4.9.3→4.10.3 для major 70; 458 тестов ✅) + +### 1.1 Обновление зависимостей (2026-08-19, все 20) +- [x] jda 6.4.2→6.5.0, lavaplayer 2.2.6→2.2.7, logback 1.5.18→1.6.3, caffeine 3.2.0→3.2.4 +- [x] sqlite 3.49.1.0→3.53.2.1, **hikaricp 6.3.0→7.1.0** (мажор, API совместим, тесты ✅), okhttp 5.4.0→5.5.0 +- [x] junit 5.12.1→5.13.4, junit-platform-launcher 1.12.1→1.13.4, mockito 5.15.2→5.23.0, bytebuddy 1.18.8→1.18.12 +- [x] **slf4j 2.1.0-alpha1 → 2.0.18** (стабильный вместо alpha) +- [x] checkstyle 10.23.0→13.10.0 (toolVersion, требует Java 21+ ✓), spotbugsPlugin 6.1.7→6.4.8, jsoup 1.19.1→1.22.2 +- [x] gson 2.14.0, guice 7.0.0, lavalinkClient 3.4.0, dotenv 3.2.0, spotbugs 4.10.3 — актуальные, без изменений + +### 2. `!generateratings` генерит только на 2 человек +- [x] **Причина**: JDA `MemberCachePolicy.DEFAULT` кэширует только голосовых/owner/self + `ChunkingFilter` по умолчанию чанкует только гильдии ≥250 участников → `guild.getMembers()` = 2 +- [x] `core/JdaFactory.java`: `.setMemberCachePolicy(MemberCachePolicy.ALL)` + `.setMemberChunkingFilter(ChunkingFilter.ALL)` +- [x] `GenerateRatingsCommand.java`: `guild.getMembers()` → `guild.loadMembers().get()` (подстраховка) + +### 3. Команда топа/низа рейтингов +- [x] `RatingsRepository`: `getTopRatings(guildId, type, limit)` + `getBottomRatings(...)` (ORDER BY value DESC/ASC LIMIT) +- [x] Новая команда `feature/rating/commands/board/RatingBoardCommand` — флейворы `ratingstop`/`ratingbottom` (высшие/низшие), рендер `<@id> — value` без резолва участников, медали топ-3 +- [x] Зарегистрировать в `feature/utility/UtilityCommandRegistrar` (рядом с GenerateRatingsCommand) + +### 4. Лимит памяти 1 ГБ +- [x] `docker-compose.yml`: сервис `bot` → `mem_limit: 1g` +- [x] `Dockerfile` ENTRYPOINT: JVM-флаги `-Xmx512m -XX:+UseG1GC -XX:+UseStringDeduplication` + +### 5. Дальнейшие оптимизации (поиск) +- [x] Изучено: Gradle 9.4+ нужен для Java 26 (9.7.0 ✓); Gradle 8.14.3 падает на major 69/70; JDA — `loadMembers()` требует GUILD_MEMBERS intent (у бота включён) +- [ ] `Database`: `PRAGMA journal_mode = WAL` +- [ ] NekosLifeApi: таймауты/ретраи в конфиг +- [ ] Негативный кэш в AnimalApi +- [ ] Свести все HttpURLConnection на OkHttp + --- ## 🔴 Результаты аудита KISS/DRY/SOLID (2026-07-04) diff --git a/build.gradle b/build.gradle index 05f9dd7..b4f6bf4 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id 'checkstyle' id 'pmd' alias(libs.plugins.spotbugs) - id 'com.diffplug.spotless' version '6.25.0' + id 'com.diffplug.spotless' version '8.10.0' } group = 'creator.loki' @@ -31,12 +31,14 @@ dependencies { testImplementation libs.junit.jupiter testImplementation libs.mockito.core testImplementation libs.mockito.junit.jupiter + testImplementation libs.byte.buddy testRuntimeOnly libs.junit.platform.launcher } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + toolchain { + languageVersion = JavaLanguageVersion.of(26) + } } tasks.withType(JavaCompile) { @@ -75,6 +77,7 @@ spotless { } checkstyle { + toolVersion = '13.10.0' configFile = file("${rootDir}/config/checkstyle/checkstyle.xml") ignoreFailures = true } @@ -86,6 +89,7 @@ pmd { } spotbugs { + toolVersion = '4.10.3' excludeFilter = file("${rootDir}/config/spotbugs/exclude.xml") ignoreFailures = false } diff --git a/docker-compose.yml b/docker-compose.yml index 3498217..9b36246 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -30,6 +30,7 @@ services: restart: unless-stopped depends_on: - lavalink + mem_limit: 1g ports: - "8712:8712" environment: diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2091ebe..e94b9a8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,23 +1,24 @@ [versions] -jda = "6.4.2" -lavaplayer = "2.2.6" +jda = "6.5.0" +lavaplayer = "2.2.7" lavalinkClient = "3.4.0" dotenv = "3.2.0" -slf4j = "2.1.0-alpha1" -logback = "1.5.18" +slf4j = "2.0.18" +logback = "1.6.3" gson = "2.14.0" -caffeine = "3.2.0" +caffeine = "3.2.4" guice = "7.0.0" -sqlite = "3.49.1.0" -hikaricp = "6.3.0" -okhttp = "5.4.0" -junit = "5.12.1" -mockito = "5.15.2" -junitPlatformLauncher = "1.12.1" -checkstyle = "10.23.0" -spotbugs = "4.9.3" -spotbugsPlugin = "6.1.7" -jsoup = "1.19.1" +sqlite = "3.53.2.1" +hikaricp = "7.1.0" +okhttp = "5.5.0" +junit = "5.13.4" +mockito = "5.23.0" +bytebuddy = "1.18.12" +junitPlatformLauncher = "1.13.4" +checkstyle = "13.10.0" +spotbugs = "4.10.3" +spotbugsPlugin = "6.4.8" +jsoup = "1.22.2" [libraries] jda = { module = "net.dv8tion:JDA", version.ref = "jda" } @@ -36,6 +37,7 @@ logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", ver junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" } mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" } +byte-buddy = { module = "net.bytebuddy:byte-buddy", version.ref = "bytebuddy" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junitPlatformLauncher" } checkstyle = { module = "com.puppycrawl.tools:checkstyle", version.ref = "checkstyle" } spotbugs = { module = "com.github.spotbugs:spotbugs", version.ref = "spotbugs" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a6..eddabd2 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 37f78a6..69dd0d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.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 af63146..379a658 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. @@ -15,21 +15,66 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob//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/. # ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link app_path=$0 + +# Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do - ls=$( ls -ld -- "$app_path" ) + ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( @@ -41,7 +86,7 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -66,15 +111,15 @@ case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( - NonStop* ) nonstop=true ;; + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java @@ -100,21 +145,104 @@ fi if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" - ;; esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" - ;; esac fi -# Collect all arguments for the java command, stracks://the current -# application classpath, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8508ef6 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +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 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +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 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@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 + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL%