diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index e9b1064..f118c3e 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -16,11 +16,11 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 11 + - name: Set up Java 17 uses: actions/setup-java@v2 with: - distribution: 'zulu' - java-version: '11' + distribution: "temurin" + java-version: 17 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle diff --git a/README.md b/README.md index 42a0c4b..7e8a21f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # BLESSED for Android with Coroutines - BLE made easy -[![](https://jitpack.io/v/weliem/blessed-android-coroutines.svg)](https://jitpack.io/#weliem/blessed-android-coroutines) +[![Jitpack Link](https://jitpack.io/v/weliem/blessed-android-coroutines.svg)](https://jitpack.io/#weliem/blessed-android-coroutines) [![Downloads](https://jitpack.io/v/weliem/blessed-android-coroutines/month.svg)](https://jitpack.io/#weliem/blessed-android-coroutines) [![Android Build](https://github.com/weliem/blessed-android-coroutines/actions/workflows/gradle.yml/badge.svg)](https://github.com/weliem/blessed-android-coroutines/actions/workflows/gradle.yml) @@ -8,7 +8,7 @@ BLESSED is a very compact Bluetooth Low Energy (BLE) library for Android 8 and h ## Installation -This library is available on Jitpack. Include the following in your gradle file: +This library is available on Jitpack. Include the following in your projects's build.gradle file: ```groovy allprojects { @@ -17,23 +17,54 @@ allprojects { maven { url 'https://jitpack.io' } } } +``` + +Include the following in your app's build.gradle file under `dependencies` block: +```groovy dependencies { + ... implementation "com.github.weliem:blessed-android-coroutines:$version" } ``` -where `$version` is the latest published version in Jitpack [![](https://jitpack.io/v/weliem/blessed-android-coroutines.svg)](https://jitpack.io/#weliem/blessed-android-coroutines) + +where `$version` is the latest published version in Jitpack [![Jitpack](https://jitpack.io/v/weliem/blessed-android-coroutines.svg)](https://jitpack.io/#weliem/blessed-android-coroutines) + +### Adding permissions + +If you plan on supporting older devices that are on Android 11 and below, then you need to add the below permissions to your AndroidManifest.xml file: + +```xml + + + + + + + + + +``` ## Overview of classes The library consists of 5 core classes and corresponding callback abstract classes: + 1. `BluetoothCentralManager`, for scanning and connecting peripherals 2. `BluetoothPeripheral`, for all peripheral related methods 3. `BluetoothPeripheralManager`, and its companion abstract class `BluetoothPeripheralManagerCallback` 4. `BluetoothCentral` 5. `BluetoothBytesParser` -The `BluetoothCentralManager` class is used to scan for devices and manage connections. The `BluetoothPeripheral` class is a replacement for the standard Android `BluetoothDevice` and `BluetoothGatt` classes. It wraps all GATT related peripheral functionality. +The `BluetoothCentralManager` class is used to scan for devices and manage connections. The `BluetoothPeripheral` class is a replacement for the standard Android `BluetoothDevice` and `BluetoothGatt` classes. It wraps all GATT related peripheral functionality. The `BluetoothPeripheralManager` class is used to create your own peripheral running on an Android phone. You can add service, control advertising, and deal with requests from remote centrals, represented by the `BluetoothCentral` class. For more about creating your own peripherals see the separate guide: [creating your own peripheral](SERVER.md) @@ -73,11 +104,10 @@ The scanning functions are not suspending functions and simply use a lambda func **Note** Only 1 of these 4 types of scans can be active at one time! So call `stopScan()` before calling another scan. - - ## Connecting to devices There are 3 ways to connect to a device: + ```kotlin suspend fun connectPeripheral(peripheral: BluetoothPeripheral): Unit fun autoConnectPeripheral(peripheral: BluetoothPeripheral) @@ -101,11 +131,13 @@ The method `autoConnectPeripheral` will **not suspend** and is for re-connecting The method `autoConnectPeripheralsBatch` is for re-connecting to multiple peripherals in one go. Since the normal `autoConnectPeripheral` may involve scanning, if peripherals are uncached, it is not suitable for calling very fast after each other, since it may trigger scanner limitations of Android. So use `autoConnectPeripheralsBatch` if you want to re-connect to many known peripherals. If you know the mac address of your peripheral you can obtain a `BluetoothPeripheral` object using: + ```kotlin val peripheral = central.getPeripheral("CF:A9:BA:D9:62:9E") ``` After issuing a connect call, you can observe the connection state of peripherals: + ```kotlin central.observeConnectionState { peripheral, state -> Timber.i("Peripheral ${peripheral.name} has $state") @@ -113,10 +145,12 @@ central.observeConnectionState { peripheral, state -> ``` To disconnect or to cancel an outstanding `connectPeripheral()` or `autoConnectPeripheral()`, you call: + ```kotlin suspend fun cancelConnection(peripheral: BluetoothPeripheral): Unit ``` -The function will suspend until the peripheral is disconnected. + +The function will suspend until the peripheral is disconnected. ## Service discovery @@ -145,10 +179,12 @@ All methods are **suspending** and will return the result of the operation. The If you want to write to a characteristic, you need to provide a `value` and a `writeType`. The `writeType` is usually `WITH_RESPONSE` or `WITHOUT_RESPONSE`. If the write type you specify is not supported by the characteristic it will throw `IllegalArgumentException`. The method will return the bytes that were written or an empty byte array in case something went wrong. There are 2 ways to specify which characteristic to use in the read/write method: + - Using its `serviceUUID` and `characteristicUUID` - Using the `BluetoothGattCharacteristic` reference directly For example: + ```kotlin peripheral.getCharacteristic(DIS_SERVICE_UUID, MANUFACTURER_NAME_CHARACTERISTIC_UUID)?.let { val manufacturerName = peripheral.readCharacteristic(it).asString() @@ -177,6 +213,7 @@ peripheral.getCharacteristic(BLP_SERVICE_UUID, BLOOD_PRESSURE_MEASUREMENT_CHARAC To stop observing notifications you call `peripheral.stopObserving(characteristic: BluetoothGattCharacteristic)` ## Bonding + BLESSED handles bonding for you and will make sure all bonding variants work smoothly. During the process of bonding, you will be informed of the process via a number of callbacks: ```kotlin @@ -184,15 +221,18 @@ peripheral.observeBondState { Timber.i("Bond state is $it") } ``` + In most cases, the peripheral will initiate bonding either at the time of connection or when trying to read/write protected characteristics. However, if you want you can also initiate bonding yourself by calling `createBond` on a peripheral. There are two ways to do this: -* Calling `createBond` when not yet connected to a peripheral. In this case, a connection is made and bonding is requested. -* Calling `createBond` when already connected to a peripheral. In this case, only the bond is created. + +- Calling `createBond` when not yet connected to a peripheral. In this case, a connection is made and bonding is requested. +- Calling `createBond` when already connected to a peripheral. In this case, only the bond is created. It is also possible to remove a bond by calling `removeBond`. Note that this method uses a hidden Android API and may stop working in the future. When calling the `removeBond` method, the peripheral will also disappear from the settings menu on the phone. Lastly, it is also possible to automatically issue a PIN code when pairing. Use the method `central.setPinCodeForPeripheral` to register a 6 digit PIN code. Once bonding starts, BLESSED will automatically issue the PIN code and the UI dialog to enter the PIN code will not appear anymore. ## Requesting a higher MTU to increase throughput + The default MTU is 23 bytes, which allows you to send and receive byte arrays of MTU - 3 = 20 bytes at a time. The 3 bytes overhead are used by the ATT packet. If your peripheral supports a higher MTU, you can request that by calling: ```kotlin @@ -205,57 +245,65 @@ If you simply want the highest possible MTU, you can call `peripheral.requestMtu Once the MTU has been set, you can always access it by calling `peripheral.currentMtu`. If you want to know the maximum length of the byte arrays that you can write, you can call the method `peripheral.getMaximumWriteValueLength()`. Note that the maximum value depends on the write type you want to use. ## Long reads and writes -The library also supports so called 'long reads/writes'. You don't need to do anything special for them. Just read a characteristic or descriptor as you normally do, and if the characteristic's value is longer than MTU - 1, then a series of reads will be done by the Android BLE stack. But you will simply receive the 'long' characteristic value in the same way as normal reads. + +The library also supports so called 'long reads/writes'. You don't need to do anything special for them. Just read a characteristic or descriptor as you normally do, and if the characteristic's value is longer than MTU - 1, then a series of reads will be done by the Android BLE stack. But you will simply receive the 'long' characteristic value in the same way as normal reads. Similarly, for long writes, you just write to a characteristic or descriptor and the Android BLE stack will take care of the rest. But keep in mind that long writes only work with `WriteType.WITH_RESPONSE` and the maximum length of your byte array should be 512 or less. Note that not all peripherals support long reads/writes so this is not guaranteed to work always. ## Status codes + When connecting or disconnecting, the callback methods will contain a parameter `HciStatus status`. This enum class will have the value `SUCCESS` if the operation succeeded and otherwise it will provide a value indicating what went wrong. Similarly, when doing GATT operations, the callbacks methods contain a parameter `GattStatus status`. These two enum classes replace the `int status` parameter that Android normally passes. ## Bluetooth 5 support + As of Android 8, Bluetooth 5 is natively supported. One of the things that Bluetooth 5 brings, is new physical layer options, called **Phy** that either give more speed or longer range. The options you can choose are: -* **LE_1M**, 1 mbit PHY, compatible with Bluetooth 4.0, 4.1, 4.2 and 5.0 -* **LE_2M**, 2 mbit PHY for higher speeds, requires Bluetooth 5.0 -* **LE_CODED**, Coded PHY for long range connections, requires Bluetooth 5.0 + +- **LE_1M**, 1 mbit PHY, compatible with Bluetooth 4.0, 4.1, 4.2 and 5.0 +- **LE_2M**, 2 mbit PHY for higher speeds, requires Bluetooth 5.0 +- **LE_CODED**, Coded PHY for long range connections, requires Bluetooth 5.0 You can set a preferred Phy by calling: + ```kotlin suspend fun setPreferredPhy(txPhy: PhyType, rxPhy: PhyType, phyOptions: PhyOptions): Phy ``` By calling `setPreferredPhy()` you indicate what you would like to have but it is not guaranteed that you get what you ask for. That depends on what the peripheral will actually support and give you. If you are requesting `LE_CODED` you can also provide PhyOptions which has 3 possible values: -* **NO_PREFERRED**, for no preference (use this when asking for LE_1M or LE_2M) -* **S2**, for 2x long range -* **S8**, for 4x long range - + +- **NO_PREFERRED**, for no preference (use this when asking for LE_1M or LE_2M) +- **S2**, for 2x long range +- **S8**, for 4x long range + The result of this negotiation will be received as a `Phy` object that is returned by `setPrefferedPhy` As you can see the Phy for sending and receiving can be different but most of the time you will see the same Phy for both. If you don't call `setPreferredPhy()`, Android seems to pick `PHY_LE_2M` if the peripheral supports Bluetooth 5. So in practice you only need to call `setPreferredPhy` if you want to use `PHY_LE_CODED`. You can request the current values at any point by calling: + ```kotlin suspend fun readPhy(): Phy ``` + It will return the current Phy ## Example application An example application is provided in the repo. It shows how to connect to Blood Pressure meters, Heart Rate monitors, Weight scales, Glucose Meters, Pulse Oximeters, and Thermometers, read the data, and show it on screen. It only works with peripherals that use the Bluetooth SIG services. Working peripherals include: -* Beurer FT95 thermometer -* GRX Thermometer (TD-1241) -* Masimo MightySat -* Nonin 3230 -* Indiehealth scale -* A&D 352BLE scale -* A&D 651BLE blood pressure meter -* Beurer BM57 blood pressure meter -* Soehnle Connect 300/400 blood pressure meter -* Polar H7/H10/OH1 heartrate monitors -* Contour Next One glucose meter -* Accu-Chek Instant glucose meter +- Beurer FT95 thermometer +- GRX Thermometer (TD-1241) +- Masimo MightySat +- Nonin 3230 +- Indiehealth scale +- A&D 352BLE scale +- A&D 651BLE blood pressure meter +- Beurer BM57 blood pressure meter +- Soehnle Connect 300/400 blood pressure meter +- Polar H7/H10/OH1 heartrate monitors +- Contour Next One glucose meter +- Accu-Chek Instant glucose meter diff --git a/app/build.gradle b/app/build.gradle index cb73a65..54550fd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,12 +2,13 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion 32 + namespace 'com.welie.blessedexample' + + compileSdk 34 defaultConfig { - applicationId "com.welie.blessedexample" - minSdkVersion 26 - targetSdkVersion 32 + minSdk 26 + targetSdk 34 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -31,13 +32,13 @@ android { dependencies { implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation 'androidx.appcompat:appcompat:1.5.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'com.jakewharton.timber:timber:5.0.1' - implementation "androidx.core:core-ktx:1.8.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" + implementation "androidx.core:core-ktx:1.12.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" implementation project(':blessed') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c5a7de4..fd8a609 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,25 @@ - + + + + + + + + + + + + - diff --git a/blessed/build.gradle b/blessed/build.gradle index 8f9ab13..a8aae7e 100644 --- a/blessed/build.gradle +++ b/blessed/build.gradle @@ -3,11 +3,14 @@ apply plugin: 'kotlin-android' apply plugin: 'maven-publish' android { - compileSdkVersion 32 + + namespace 'com.welie.blessed' + + compileSdk 34 defaultConfig { - minSdkVersion 26 - targetSdkVersion 32 + minSdk 26 + targetSdk 34 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -20,26 +23,26 @@ android { } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } } dependencies { - implementation "androidx.core:core-ktx:1.8.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.1" + implementation "androidx.core:core-ktx:1.12.0" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" implementation 'com.jakewharton.timber:timber:5.0.1' testImplementation 'junit:junit:4.13.2' testImplementation "org.robolectric:robolectric:4.5.1" testImplementation "org.mockito:mockito-core:3.8.0" - testImplementation 'androidx.test:core:1.4.0' - testImplementation "io.mockk:mockk:1.12.2" + testImplementation 'androidx.test:core:1.5.0' + testImplementation "io.mockk:mockk:1.13.8" } afterEvaluate { diff --git a/blessed/src/main/AndroidManifest.xml b/blessed/src/main/AndroidManifest.xml index 65b2938..7c51bb7 100644 --- a/blessed/src/main/AndroidManifest.xml +++ b/blessed/src/main/AndroidManifest.xml @@ -1,24 +1,24 @@ + xmlns:tools="http://schemas.android.com/tools"> - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.kt b/blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.kt index 9cb7075..08d7ab9 100644 --- a/blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.kt +++ b/blessed/src/main/java/com/welie/blessed/BluetoothPeripheral.kt @@ -337,7 +337,7 @@ class BluetoothPeripheral internal constructor( // See if the initial connection failed if (previousState == BluetoothProfile.STATE_CONNECTING) { val timePassed = SystemClock.elapsedRealtime() - connectTimestamp - val isTimeout = timePassed > timoutThreshold + val isTimeout = timePassed > timeoutThreshold val adjustedStatus = if (status == HciStatus.ERROR && isTimeout) HciStatus.CONNECTION_FAILED_ESTABLISHMENT else status Logger.d(TAG, "connection failed with status '%s'", adjustedStatus) completeDisconnect(false, adjustedStatus) @@ -693,7 +693,7 @@ class BluetoothPeripheral internal constructor( * Get the BluetoothGattCharacteristic object for a characteristic UUID. * * @param serviceUUID the service UUID the characteristic is part of - * @param characteristicUUID the UUID of the chararacteristic + * @param characteristicUUID the UUID of the characteristic * @return the BluetoothGattCharacteristic object for the characteristic UUID or null if the peripheral does not have a characteristic with the specified UUID */ fun getCharacteristic(serviceUUID: UUID, characteristicUUID: UUID): BluetoothGattCharacteristic? { @@ -898,7 +898,7 @@ class BluetoothPeripheral internal constructor( * @param characteristic the characteristic to write to * @param value the byte array to write * @param writeType the write type to use when writing. - * @return true if a write operation was succesfully enqueued, otherwise false + * @return true if a write operation was successfully enqueued, otherwise false */ private fun writeCharacteristic(characteristic: BluetoothGattCharacteristic, value: ByteArray, writeType: WriteType, resultCallback: BluetoothPeripheralCallback): Boolean { require(isConnected) { PERIPHERAL_NOT_CONNECTED } @@ -968,7 +968,7 @@ class BluetoothPeripheral internal constructor( * Read the value of a descriptor. * * @param descriptor the descriptor to read - * @return true if a write operation was succesfully enqueued, otherwise false + * @return true if a write operation was successfully enqueued, otherwise false */ private fun readDescriptor(descriptor: BluetoothGattDescriptor, resultCallback: BluetoothPeripheralCallback): Boolean { require(isConnected) { PERIPHERAL_NOT_CONNECTED } @@ -1020,7 +1020,7 @@ class BluetoothPeripheral internal constructor( * * @param descriptor the descriptor to write to * @param value the value to write - * @return true if a write operation was succesfully enqueued, otherwise false + * @return true if a write operation was successfully enqueued, otherwise false */ private fun writeDescriptor(descriptor: BluetoothGattDescriptor, value: ByteArray, resultCallback: BluetoothPeripheralCallback): Boolean { require(isConnected) { PERIPHERAL_NOT_CONNECTED } @@ -1537,7 +1537,7 @@ class BluetoothPeripheral internal constructor( timeoutJob = scope.launch { delay(CONNECTION_TIMEOUT_IN_MS) - Logger.e(TAG, "connection timout, disconnecting '%s'", peripheral.name) + Logger.e(TAG, "connection timeout, disconnecting '%s'", peripheral.name) disconnect() scope.launch { delay(50) @@ -1553,7 +1553,7 @@ class BluetoothPeripheral internal constructor( timeoutJob = null } - private val timoutThreshold: Int + private val timeoutThreshold: Int get() { val manufacturer = Build.MANUFACTURER return if (manufacturer.equals("samsung", ignoreCase = true)) { diff --git a/build.gradle b/build.gradle index 2b64d4a..273a7d1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,14 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.9.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.2' + classpath 'com.android.tools.build:gradle:8.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong @@ -20,6 +20,7 @@ allprojects { repositories { google() mavenCentral() + maven { url 'https://jitpack.io' } } } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180..7f93135 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 2e6e589..8838ba9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 3da45c1..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright ? 2015-2021 the original authors. +# Copyright © 2015-2021 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. @@ -32,10 +32,10 @@ # 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?. +# * 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: # @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# 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,22 +131,29 @@ 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. 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 @@ -193,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" \ @@ -205,6 +214,12 @@ set -- \ org.gradle.wrapper.GradleWrapperMain \ "$@" +# 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. diff --git a/gradlew.bat b/gradlew.bat index ac1b06f..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +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! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +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