diff --git a/app/build.gradle b/app/build.gradle index 6e37fc6e1..ea26b826d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,14 +1,8 @@ -import com.github.spotbugs.SpotBugsTask - import java.nio.file.Files apply plugin: "com.android.application" -apply plugin: "com.github.spotbugs" -apply plugin: "pmd" apply plugin: 'com.google.protobuf' -def ABORT_ON_CHECK_FAILURE = true - tasks.withType(Test) { systemProperty "MiFirmwareDir", System.getProperty("MiFirmwareDir", null) systemProperty "logback.configurationFile", System.getProperty("user.dir", null) + "/app/src/main/assets/logback.xml" @@ -147,7 +141,7 @@ android { versionNameSuffix "-${getGitHashShort}" proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" minifyEnabled true - debuggable true + debuggable false if (System.getProperty("nightly_store_file") != null) { signingConfig signingConfigs.nightly @@ -161,7 +155,7 @@ android { versionNameSuffix "-${getGitHashShort}" proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" minifyEnabled true - debuggable true + debuggable false if (System.getProperty("nightly_store_file") != null) { signingConfig signingConfigs.nightly @@ -186,7 +180,7 @@ android { } lint { - abortOnError ABORT_ON_CHECK_FAILURE + abortOnError true lintConfig file("$rootDir/app/src/main/lint.xml") // If true, generate an HTML report (with issue explanations, sourcecode, etc) htmlReport true @@ -202,10 +196,10 @@ android { includeAndroidResources = true } } -} - -pmd { - toolVersion = "5.5.5" + buildFeatures { + aidl true + buildConfig true + } } dependencies { @@ -291,49 +285,6 @@ gradle.beforeProject { preBuild.dependsOn(":GBDaoGenerator:genSources") } -check.dependsOn "spotbugsMain", "pmd", "lint" - -task pmd(type: Pmd) { - ruleSetFiles = files("${project.rootDir}/config/pmd/pmd-ruleset.xml") - ignoreFailures = !ABORT_ON_CHECK_FAILURE - ruleSets = [ - "java-android", - "java-basic", - "java-braces", - "java-clone", - "java-codesize", - "java-controversial", - "java-coupling", - "java-design", - "java-empty", - "java-finalizers", - "java-imports", - "java-junit", - "java-optimizations", - "java-strictexception", - "java-strings", - "java-sunsecure", - "java-typeresolution", - "java-unnecessary", - "java-unusedcode" - ] - - source "src" - include "**/*.java" - exclude "**/gen/**" - - reports { - xml.enabled = false - html.enabled = true - xml { - destination file("$project.buildDir/reports/pmd/pmd.xml") - } - html { - destination file("$project.buildDir/reports/pmd/pmd.html") - } - } -} - sourceSets { main { java.srcDirs += "${protobuf.generatedFilesBaseDir}" @@ -341,30 +292,7 @@ sourceSets { } } -spotbugs { - toolVersion = "3.1.12" - ignoreFailures = !ABORT_ON_CHECK_FAILURE - effort = "default" - reportLevel = "medium" -} - -tasks.withType(SpotBugsTask) { - source = fileTree('src/main/java') - classes = files("${project.rootDir}/app/build/intermediates/javac/debug/classes") - excludeFilter = new File("${project.rootDir}/config/findbugs/findbugs-filter.xml") - reports { - xml.enabled = false - html.enabled = true - xml { - destination file("$project.buildDir/reports/spotbugs/spotbugs-output.xml") - } - html { - destination file("$project.buildDir/reports/spotbugs/spotbugs-output.html") - } - } -} - -task cleanGenerated(type: Delete) { +tasks.register('cleanGenerated', Delete) { delete fileTree('src/main/java/nodomain/freeyourgadget/gadgetbridge/entities') { include '**/*.java' exclude '**/Abstract*.java' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 57b7817cf..6ff419080 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -34,6 +34,9 @@ } -keepattributes JavascriptInterface +# Keep coordinators, they're only referenced from DeviceType +-keep public class * implements nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator + # Keep parseIncoming for GFDIMessage classes, as it is called by reflection in GFDIMessage#parseIncoming -keep public class * extends nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage -keepclassmembers class * extends nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.messages.GFDIMessage { @@ -63,6 +66,10 @@ # Keep dependency android-emojify (io.wax911.emojify) uses -keep class org.hamcrest.** { *; } +-dontwarn java.beans.BeanInfo +-dontwarn java.beans.IntrospectionException +-dontwarn java.beans.Introspector +-dontwarn java.beans.PropertyDescriptor # Keep logback classes -keep class ch.qos.** { *; } @@ -72,6 +79,8 @@ -keepclassmembers,allowobfuscation class * { @com.google.gson.annotations.SerializedName ; } +# Somehow the rule above was not enough for some +-keep class nodomain.freeyourgadget.gadgetbridge.devices.pinetime.InfiniTimeDFU* { *; } # Keep generated protobuf classes -keep class nodomain.freeyourgadget.gadgetbridge.proto.** { *; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java index 37a5ebc08..9bad04260 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenterv2.java @@ -222,7 +222,7 @@ public class ControlCenterv2 extends AppCompatActivity if (GBApplication.areDynamicColorsEnabled()) { TypedValue typedValue = new TypedValue(); Resources.Theme theme = getTheme(); - theme.resolveAttribute(R.attr.colorSurface, typedValue, true); + theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true); @ColorInt int toolbarBackground = typedValue.data; toolbar.setBackgroundColor(toolbarBackground); } else { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java index 65e1c5674..a4b0ffcec 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartsActivity.java @@ -269,16 +269,14 @@ public abstract class AbstractChartsActivity extends AbstractGBFragmentActivity @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.charts_fetch_activity_data: - fetchRecordedData(); - return true; - case R.id.prefs_charts_menu: - Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class); - startActivityForResult(settingsIntent, REQUEST_CODE_PREFERENCES); - return true; - default: - break; + final int itemId = item.getItemId(); + if (itemId == R.id.charts_fetch_activity_data) { + fetchRecordedData(); + return true; + } else if (itemId == R.id.prefs_charts_menu) { + Intent settingsIntent = new Intent(this, ChartsPreferencesActivity.class); + startActivityForResult(settingsIntent, REQUEST_CODE_PREFERENCES); + return true; } return super.onOptionsItemSelected(item); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index 79bc970d6..eab493a70 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -79,7 +79,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.Spo2Sample; import nodomain.freeyourgadget.gadgetbridge.model.StressSample; import nodomain.freeyourgadget.gadgetbridge.model.TemperatureSample; import nodomain.freeyourgadget.gadgetbridge.service.ServiceDeviceSupport; -import nodomain.freeyourgadget.gadgetbridge.service.SleepAsAndroidSender; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; @@ -107,7 +106,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { supportedDeviceName = getSupportedDeviceName(); } if (supportedDeviceName == null) { - LOG.error(getClass() + " should either override getSupportedDeviceName or supports(GBDeviceCandidate)"); + LOG.error("{} should either override getSupportedDeviceName or supports(GBDeviceCandidate)", getClass()); return false; } @@ -142,7 +141,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { @Override public final void deleteDevice(final GBDevice gbDevice) throws GBException { - LOG.info("will try to delete device: " + gbDevice.getName()); + LOG.info("will try to delete device: {}", gbDevice.getName()); if (gbDevice.isConnected() || gbDevice.isConnecting()) { GBApplication.deviceService(gbDevice).disconnect(); } @@ -177,7 +176,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { alarmDeviceQueryBuilder.where(AlarmDao.Properties.DeviceId.eq(device.getId())).buildDelete().executeDeleteWithoutDetachingEntities(); session.getDeviceDao().delete(device); } else { - LOG.info("device to delete not found in db: " + gbDevice); + LOG.info("device to delete not found in db: {}", gbDevice); } } catch (Exception e) { throw new GBException("Error deleting device: " + e.getMessage(), e); @@ -190,7 +189,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { * @param gbDevice the GBDevice * @param device the corresponding database Device * @param session the session to use - * @throws GBException + * @throws GBException if there was an error deleting device-specific resources */ protected abstract void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException; @@ -273,7 +272,7 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { return false; } if (bluetoothClass == null) { - LOG.warn("unable to determine bluetooth device class of " + device); + LOG.warn("unable to determine bluetooth device class of {}", device); return false; } if (bluetoothClass.getMajorDeviceClass() == BluetoothClass.Device.Major.WEARABLE diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java index 4d7c07253..d1fc793f8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/jyou/BFH16DeviceCoordinator.java @@ -55,6 +55,7 @@ public class BFH16DeviceCoordinator extends AbstractBLEDeviceCoordinator return "Denver"; } + @NonNull @Override public Collection createBLEScanFilters() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java index df731369d..d3d3e4d34 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java @@ -101,7 +101,7 @@ public class MiBandCoordinator extends AbstractBLEDeviceCoordinator { } @Override - protected void deleteDevice(GBDevice gbDevice, Device device, DaoSession session) throws GBException { + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { Long deviceId = device.getId(); QueryBuilder qb = session.getMiBandActivitySampleDao().queryBuilder(); qb.where(MiBandActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java index 6250ef76c..52ac08c32 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pinetime/InfiniTimeDFUPackage.java @@ -21,6 +21,9 @@ import com.google.gson.annotations.SerializedName; import java.math.BigInteger; import java.util.List; +/** + * If this or any of the classes below are renamed, update the proguard rules. + */ public class InfiniTimeDFUPackage { @SerializedName("manifest") InfiniTimeDFUPackageManifest manifest; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java index 610c4acb8..12d0ba191 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java @@ -52,7 +52,7 @@ public class VescCoordinator extends AbstractBLEDeviceCoordinator { @Override - protected void deleteDevice(GBDevice gbDevice, Device device, DaoSession session) throws GBException { + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java index 4110bbfaa..8d36a6369 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/AndroidUtils.java @@ -126,7 +126,7 @@ public class AndroidUtils { } else { dynamicColorContext = DynamicColors.wrapContextIfAvailable(context, R.style.GadgetbridgeThemeDynamicLight); } - int[] attrsToResolve = {R.attr.colorOnSurface}; + int[] attrsToResolve = {com.google.android.material.R.attr.colorOnSurface}; @SuppressLint("ResourceType") TypedArray ta = dynamicColorContext.obtainStyledAttributes(attrsToResolve); color = ta.getColor(0, 0); @@ -152,15 +152,15 @@ public class AndroidUtils { } else { dynamicColorContext = DynamicColors.wrapContextIfAvailable(context, R.style.GadgetbridgeThemeDynamicLight); } - int[] attrsToResolve = {R.attr.colorSurface}; + int[] attrsToResolve = {com.google.android.material.R.attr.colorSurface}; @SuppressLint("ResourceType") TypedArray ta = dynamicColorContext.obtainStyledAttributes(attrsToResolve); color = ta.getColor(0, 0); ta.recycle(); } else if (GBApplication.isDarkThemeEnabled()) { - color = context.getResources().getColor(R.color.cardview_dark_background); + color = context.getResources().getColor(androidx.cardview.R.color.cardview_dark_background); } else { - color = context.getResources().getColor(R.color.cardview_light_background); + color = context.getResources().getColor(androidx.cardview.R.color.cardview_light_background); } return colorToHex(color); } @@ -168,9 +168,9 @@ public class AndroidUtils { public static int getBackgroundColor(Context context) { int color; if (GBApplication.isDarkThemeEnabled()) { - color = context.getResources().getColor(R.color.cardview_dark_background); + color = context.getResources().getColor(androidx.cardview.R.color.cardview_dark_background); } else { - color = context.getResources().getColor(R.color.cardview_light_background); + color = context.getResources().getColor(androidx.cardview.R.color.cardview_light_background); } return color; } diff --git a/build.gradle b/build.gradle index 2599ff277..40c5146bb 100644 --- a/build.gradle +++ b/build.gradle @@ -9,9 +9,8 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:8.5.0' - classpath 'gradle.plugin.com.github.spotbugs:spotbugs-gradle-plugin:2.0.0' classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.4' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/config/findbugs/findbugs-filter.xml b/config/findbugs/findbugs-filter.xml deleted file mode 100644 index 1b3b80c47..000000000 --- a/config/findbugs/findbugs-filter.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/config/pmd/pmd-ruleset.xml b/config/pmd/pmd-ruleset.xml deleted file mode 100644 index dd9ab574c..000000000 --- a/config/pmd/pmd-ruleset.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - Custom ruleset for Android application - - .*/R.java - .*/gen/.* - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 872fcadff..d7d4c9722 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,3 +19,9 @@ org.gradle.jvmargs=-Xmx2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF # org.gradle.parallel=true android.useAndroidX=true android.enableJetifier=true + +# FIXME: Migrate all switches to if statements +android.nonFinalResIds=false + +# FIXME: This optimizes away some classes it should not - see #3853 +android.enableR8.fullMode=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa..e6441136f 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 070cb702f..074dbc48a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d6..b740cf133 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/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/. @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# 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"' +# 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 # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ 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=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# 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, 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" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85be..7101f8e46 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +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 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +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 goto fail