Fix API correctness issues found during static audit
- GeminiNanoModel: Rewrite to use actual ML Kit Prompt API
(Generation.getClient(), checkStatus(), download(), warmup())
- MediaPipeModel: Fix streaming to use sync fallback since
MediaPipe requires result listener set at build time
- ApiModels: Add @SerializedName("object") for OpenAI compat,
add "created" timestamp to ChatResponse/StreamChunk
- settings.gradle.kts: Fix dependencyResolutionManagement typo
- MainActivity: Use GradientDrawable.setColor() to preserve
oval shape on status dot
- Add Gradle wrapper scripts (gradlew, gradlew.bat, jar)
https://claude.ai/code/session_01GvqMLSMmfMR8uz66BFVXX2
This commit is contained in:
@@ -2,17 +2,16 @@ package com.pixel10.ai.inference
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.mlkit.genai.prompt.DownloadStatus
|
||||
import com.google.mlkit.genai.prompt.FeatureStatus
|
||||
import com.google.mlkit.genai.prompt.Generation
|
||||
import com.google.mlkit.genai.prompt.GenerativeModel
|
||||
import com.google.mlkit.genai.prompt.type.Content
|
||||
import com.google.mlkit.genai.prompt.type.TextPart
|
||||
import com.google.mlkit.genai.prompt.type.content
|
||||
import com.google.mlkit.genai.prompt.type.generationConfig
|
||||
import com.google.mlkit.genai.prompt.type.generateContentRequest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.fold
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
/**
|
||||
* Gemini Nano backend via ML Kit Prompt API.
|
||||
@@ -22,10 +21,9 @@ import kotlin.coroutines.resumeWithException
|
||||
* no manual download or file management needed.
|
||||
*
|
||||
* Key advantages:
|
||||
* - Hardware-accelerated on Tensor G5 TPU (2.6x faster than G4)
|
||||
* - 32,000 token context window on Pixel 10
|
||||
* - ~3 GB model always resident in RAM for instant inference
|
||||
* - Hardware-accelerated on Tensor G5 TPU
|
||||
* - Fully offline, private — data never leaves the device
|
||||
* - System-managed model, no manual downloads
|
||||
*/
|
||||
class GeminiNanoModel private constructor(
|
||||
private val generativeModel: GenerativeModel
|
||||
@@ -43,9 +41,12 @@ class GeminiNanoModel private constructor(
|
||||
temperature: Float
|
||||
): String = withContext(Dispatchers.Default) {
|
||||
try {
|
||||
val request = content { text(prompt) }
|
||||
val request = generateContentRequest(TextPart(prompt)) {
|
||||
this.temperature = temperature
|
||||
this.topK = 40
|
||||
}
|
||||
val response = generativeModel.generateContent(request)
|
||||
response.text ?: ""
|
||||
response.candidates.firstOrNull()?.text ?: ""
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Gemini Nano generation error", e)
|
||||
throw OnDeviceModel.InferenceException("Gemini Nano generation failed: ${e.message}", e)
|
||||
@@ -57,12 +58,11 @@ class GeminiNanoModel private constructor(
|
||||
onToken: (String) -> Unit
|
||||
): String = withContext(Dispatchers.Default) {
|
||||
try {
|
||||
val request = content { text(prompt) }
|
||||
generativeModel.generateContentStream(request)
|
||||
.fold("") { acc, response ->
|
||||
val chunk = response.text ?: ""
|
||||
if (chunk.isNotEmpty()) onToken(chunk)
|
||||
acc + chunk
|
||||
generativeModel.generateContentStream(prompt)
|
||||
.fold("") { acc, chunk ->
|
||||
val text = chunk.candidates.firstOrNull()?.text ?: ""
|
||||
if (text.isNotEmpty()) onToken(text)
|
||||
acc + text
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Gemini Nano streaming error", e)
|
||||
@@ -72,53 +72,60 @@ class GeminiNanoModel private constructor(
|
||||
|
||||
override fun close() {
|
||||
isReady = false
|
||||
generativeModel.close()
|
||||
// GenerativeModel from Generation.getClient() is system-managed
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "GeminiNanoModel"
|
||||
|
||||
suspend fun create(context: Context): GeminiNanoModel = withContext(Dispatchers.IO) {
|
||||
// Check if Gemini Nano is available on this device
|
||||
val model = GenerativeModel.newBuilder()
|
||||
.setContext(context)
|
||||
.build()
|
||||
val model = Generation.getClient()
|
||||
|
||||
// Verify feature is available — will throw if not supported
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
model.isAvailable()
|
||||
.addOnSuccessListener { available ->
|
||||
if (available) {
|
||||
continuation.resume(Unit)
|
||||
} else {
|
||||
continuation.resumeWithException(
|
||||
OnDeviceModel.InferenceException(
|
||||
// Check if Gemini Nano is available on this device
|
||||
val status = model.checkStatus()
|
||||
when (status) {
|
||||
FeatureStatus.UNAVAILABLE -> {
|
||||
throw OnDeviceModel.InferenceException(
|
||||
"Gemini Nano is not available on this device"
|
||||
)
|
||||
)
|
||||
}
|
||||
FeatureStatus.DOWNLOADABLE -> {
|
||||
Log.i(TAG, "Downloading Gemini Nano model...")
|
||||
model.download().collect { downloadStatus ->
|
||||
when (downloadStatus) {
|
||||
is DownloadStatus.DownloadStarted ->
|
||||
Log.i(TAG, "Model download started")
|
||||
is DownloadStatus.DownloadProgress ->
|
||||
Log.i(TAG, "Download in progress...")
|
||||
DownloadStatus.DownloadCompleted ->
|
||||
Log.i(TAG, "Model download completed")
|
||||
is DownloadStatus.DownloadFailed ->
|
||||
throw OnDeviceModel.InferenceException("Model download failed")
|
||||
}
|
||||
}
|
||||
.addOnFailureListener { e ->
|
||||
continuation.resumeWithException(
|
||||
OnDeviceModel.InferenceException(
|
||||
"Failed to check Gemini Nano availability: ${e.message}", e
|
||||
)
|
||||
)
|
||||
}
|
||||
FeatureStatus.DOWNLOADING -> {
|
||||
Log.i(TAG, "Model already downloading, waiting...")
|
||||
model.download().collect { downloadStatus ->
|
||||
if (downloadStatus == DownloadStatus.DownloadCompleted) {
|
||||
Log.i(TAG, "Download completed")
|
||||
}
|
||||
}
|
||||
}
|
||||
FeatureStatus.AVAILABLE -> {
|
||||
Log.i(TAG, "Gemini Nano is available")
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger model download if needed
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
model.downloadModel()
|
||||
.addOnSuccessListener { continuation.resume(Unit) }
|
||||
.addOnFailureListener { e ->
|
||||
Log.w(TAG, "Model download issue (may already be available): ${e.message}")
|
||||
// Don't fail — model might already be cached
|
||||
continuation.resume(Unit)
|
||||
}
|
||||
// Warm up for lower first-inference latency
|
||||
try {
|
||||
model.warmup()
|
||||
Log.i(TAG, "Model warmup complete")
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Warmup failed (non-fatal): ${e.message}")
|
||||
}
|
||||
|
||||
Log.i(TAG, "Gemini Nano model ready via ML Kit Prompt API")
|
||||
Log.i(TAG, "Gemini Nano ready via ML Kit Prompt API")
|
||||
GeminiNanoModel(model)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,8 @@ import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.mediapipe.tasks.genai.llminference.LlmInference
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
|
||||
/**
|
||||
* MediaPipe LLM Inference backend for custom open-weight models.
|
||||
@@ -52,23 +49,18 @@ class MediaPipeModel private constructor(
|
||||
prompt: String,
|
||||
onToken: (String) -> Unit
|
||||
): String = withContext(Dispatchers.Default) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val fullResponse = StringBuilder()
|
||||
// MediaPipe's streaming API (generateResponseAsync) requires the result
|
||||
// listener to be set at LlmInference build time via setResultListener().
|
||||
// Since our architecture needs a dynamic callback per request, we use
|
||||
// synchronous generation and emit the result as a single chunk.
|
||||
// For true token-by-token streaming, the Gemini Nano backend is preferred.
|
||||
try {
|
||||
llmInference.generateResponseAsync(prompt).addResultListener { partialResult, done ->
|
||||
val chunk = partialResult ?: ""
|
||||
fullResponse.append(chunk)
|
||||
onToken(chunk)
|
||||
if (done) {
|
||||
continuation.resume(fullResponse.toString())
|
||||
}
|
||||
}
|
||||
val result = llmInference.generateResponse(prompt)
|
||||
onToken(result)
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Streaming error", e)
|
||||
continuation.resumeWithException(
|
||||
OnDeviceModel.InferenceException("Streaming failed: ${e.message}", e)
|
||||
)
|
||||
}
|
||||
Log.e(TAG, "MediaPipe generation error", e)
|
||||
throw OnDeviceModel.InferenceException("Generation failed: ${e.message}", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,7 +112,6 @@ class MediaPipeModel private constructor(
|
||||
}
|
||||
|
||||
private fun findModelPath(context: Context): String? {
|
||||
// Search standard locations
|
||||
val searchDirs = listOfNotNull(
|
||||
context.filesDir,
|
||||
File(context.filesDir, "models"),
|
||||
@@ -137,7 +128,6 @@ class MediaPipeModel private constructor(
|
||||
return file.absolutePath
|
||||
}
|
||||
}
|
||||
// Also check for any .task or .bin file
|
||||
dir.listFiles()?.firstOrNull {
|
||||
it.extension in listOf("task", "bin", "tflite")
|
||||
}?.let { return it.absolutePath }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.pixel10.ai.server
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* Request/response models for the AI API.
|
||||
* Follows an OpenAI-compatible schema for easy integration.
|
||||
@@ -20,6 +22,9 @@ data class Message(
|
||||
|
||||
data class ChatResponse(
|
||||
val id: String,
|
||||
@SerializedName("object")
|
||||
val objectType: String = "chat.completion",
|
||||
val created: Long = System.currentTimeMillis() / 1000,
|
||||
val model: String = "pixel10-on-device",
|
||||
val choices: List<Choice>,
|
||||
val usage: Usage
|
||||
@@ -39,6 +44,9 @@ data class Usage(
|
||||
|
||||
data class StreamChunk(
|
||||
val id: String,
|
||||
@SerializedName("object")
|
||||
val objectType: String = "chat.completion.chunk",
|
||||
val created: Long = System.currentTimeMillis() / 1000,
|
||||
val model: String = "pixel10-on-device",
|
||||
val choices: List<StreamChoice>
|
||||
)
|
||||
@@ -56,12 +64,15 @@ data class Delta(
|
||||
|
||||
data class ModelInfo(
|
||||
val id: String = "pixel10-on-device",
|
||||
val object_type: String = "model",
|
||||
@SerializedName("object")
|
||||
val objectType: String = "model",
|
||||
val owned_by: String = "local-device",
|
||||
val description: String = "On-device AI model running on Pixel 10 Tensor G5 chip"
|
||||
)
|
||||
|
||||
data class ModelList(
|
||||
@SerializedName("object")
|
||||
val objectType: String = "list",
|
||||
val data: List<ModelInfo> = listOf(ModelInfo())
|
||||
)
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -139,7 +140,7 @@ class MainActivity : AppCompatActivity() {
|
||||
when (state) {
|
||||
ApiServerService.ServerState.STOPPED -> {
|
||||
binding.tvServerStatus.text = getString(R.string.server_status_stopped)
|
||||
binding.viewStatusDot.setBackgroundColor(getColor(R.color.status_stopped))
|
||||
(binding.viewStatusDot.background as? GradientDrawable)?.setColor(getColor(R.color.status_stopped))
|
||||
binding.tvServerUrl.text = "http://—"
|
||||
binding.tvModelStatus.text = "Model: not loaded"
|
||||
binding.btnToggle.text = getString(R.string.btn_start)
|
||||
@@ -148,7 +149,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
ApiServerService.ServerState.LOADING_MODEL -> {
|
||||
binding.tvServerStatus.text = getString(R.string.server_status_starting)
|
||||
binding.viewStatusDot.setBackgroundColor(getColor(R.color.primary))
|
||||
(binding.viewStatusDot.background as? GradientDrawable)?.setColor(getColor(R.color.primary))
|
||||
binding.tvModelStatus.text = getString(R.string.model_loading)
|
||||
binding.btnToggle.isEnabled = false
|
||||
binding.etPort.isEnabled = false
|
||||
@@ -157,7 +158,7 @@ class MainActivity : AppCompatActivity() {
|
||||
val port = binding.etPort.text.toString()
|
||||
val ip = getLocalIpAddress()
|
||||
binding.tvServerStatus.text = getString(R.string.server_status_running)
|
||||
binding.viewStatusDot.setBackgroundColor(getColor(R.color.status_running))
|
||||
(binding.viewStatusDot.background as? GradientDrawable)?.setColor(getColor(R.color.status_running))
|
||||
binding.tvServerUrl.text = "http://$ip:$port"
|
||||
binding.tvModelStatus.text = getString(R.string.model_ready)
|
||||
binding.btnToggle.text = getString(R.string.btn_stop)
|
||||
@@ -166,7 +167,7 @@ class MainActivity : AppCompatActivity() {
|
||||
}
|
||||
ApiServerService.ServerState.ERROR -> {
|
||||
binding.tvServerStatus.text = getString(R.string.server_status_error)
|
||||
binding.viewStatusDot.setBackgroundColor(getColor(R.color.error))
|
||||
(binding.viewStatusDot.background as? GradientDrawable)?.setColor(getColor(R.color.error))
|
||||
binding.tvModelStatus.text = getString(R.string.model_error)
|
||||
binding.btnToggle.text = getString(R.string.btn_start)
|
||||
binding.btnToggle.isEnabled = true
|
||||
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
137
gradlew
vendored
Executable file
137
gradlew
vendored
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# 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.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld -- "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in
|
||||
/*) app_path=$link ;;
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# 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
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in
|
||||
CYGWIN* ) cygwin=true ;;
|
||||
Darwin* ) darwin=true ;;
|
||||
MSYS* | MINGW* ) msys=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
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
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*)
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
;;
|
||||
esac
|
||||
case $MAX_FD in
|
||||
'' | soft) :;;
|
||||
*)
|
||||
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://github.com/gradle/gradle/issues/25036)
|
||||
# shellcheck disable=SC2153
|
||||
case $( dirname -- "$0" ) in
|
||||
'' ) set -- org.gradle.wrapper.GradleWrapperMain "$@" ;;
|
||||
*) set -- org.gradle.wrapper.GradleWrapperMain "$@" ;;
|
||||
esac
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$DEFAULT_JVM_OPTS \
|
||||
$JAVA_OPTS \
|
||||
$GRADLE_OPTS \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
80
gradlew.bat
vendored
Normal file
80
gradlew.bat
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
@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
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
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
|
||||
|
||||
goto fail
|
||||
|
||||
: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
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %OS%==Windows_NT endlocal
|
||||
|
||||
:omega
|
||||
@@ -6,7 +6,7 @@ pluginManagement {
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolution {
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
||||
Reference in New Issue
Block a user