mirror of https://github.com/grpc/grpc.git
Merge pull request #14193 from ericgribkoff/android_cpp_helloworld
C++ on Android: Hello World client and serverpull/14583/merge
commit
f9244afe4d
25 changed files with 1027 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||||
|
*.iml |
||||||
|
.gradle |
||||||
|
/local.properties |
||||||
|
/.idea/workspace.xml |
||||||
|
/.idea/libraries |
||||||
|
.DS_Store |
||||||
|
/build |
||||||
|
/captures |
||||||
|
.externalNativeBuild |
@ -0,0 +1,24 @@ |
|||||||
|
gRPC on Android |
||||||
|
============== |
||||||
|
|
||||||
|
Note: Building the protobuf dependency for Android requires |
||||||
|
https://github.com/google/protobuf/pull/3878. This fix will be in the next |
||||||
|
protobuf release, but until then must be manually patched in to |
||||||
|
`third_party/protobuf` to build gRPC for Android. |
||||||
|
|
||||||
|
PREREQUISITES |
||||||
|
------------- |
||||||
|
|
||||||
|
- Android SDK |
||||||
|
- Android NDK |
||||||
|
- `protoc` and `grpc_cpp_plugin` binaries on the host system |
||||||
|
|
||||||
|
INSTALL |
||||||
|
------- |
||||||
|
|
||||||
|
The example application can be built via Android Studio or on the command line |
||||||
|
using `gradle`: |
||||||
|
|
||||||
|
```sh |
||||||
|
$ ./gradlew installDebug |
||||||
|
``` |
@ -0,0 +1 @@ |
|||||||
|
/build |
@ -0,0 +1,123 @@ |
|||||||
|
cmake_minimum_required(VERSION 3.4.1) |
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") |
||||||
|
|
||||||
|
set(helloworld_PROTOBUF_PROTOC_EXECUTABLE "/usr/local/bin/protoc" CACHE STRING "Protoc binary on host") |
||||||
|
set(helloworld_GRPC_CPP_PLUGIN_EXECUTABLE "/usr/local/bin/grpc_cpp_plugin" CACHE STRING "gRPC CPP plugin binary on host") |
||||||
|
|
||||||
|
set(GRPC_SRC_DIR ../../../../) |
||||||
|
|
||||||
|
set(GRPC_BUILD_DIR ../grpc/outputs/${ANDROID_ABI}) |
||||||
|
file(MAKE_DIRECTORY ${GRPC_BUILD_DIR}) |
||||||
|
|
||||||
|
add_subdirectory(${GRPC_SRC_DIR} ${GRPC_BUILD_DIR}) |
||||||
|
|
||||||
|
include_directories(${GRPC_SRC_DIR}/include) |
||||||
|
|
||||||
|
add_library(libgrpc STATIC IMPORTED) |
||||||
|
set_target_properties(libgrpc PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/libgrpc.a) |
||||||
|
|
||||||
|
add_library(libgrpc++ STATIC IMPORTED) |
||||||
|
set_target_properties(libgrpc++ PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/libgrpc++.a) |
||||||
|
|
||||||
|
add_library(libgpr STATIC IMPORTED) |
||||||
|
set_target_properties(libgpr PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/libgpr.a) |
||||||
|
|
||||||
|
add_library(libcares STATIC IMPORTED) |
||||||
|
set_target_properties(libcares PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/third_party/cares/cares/lib/libcares.a) |
||||||
|
|
||||||
|
add_library(libzlib STATIC IMPORTED) |
||||||
|
set_target_properties(libzlib PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/third_party/zlib/libz.a) |
||||||
|
|
||||||
|
add_library(libcrypto STATIC IMPORTED) |
||||||
|
set_target_properties(libcrypto PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/third_party/boringssl/crypto/libcrypto.a) |
||||||
|
|
||||||
|
add_library(libssl STATIC IMPORTED) |
||||||
|
set_target_properties(libssl PROPERTIES IMPORTED_LOCATION |
||||||
|
${GRPC_BUILD_DIR}/third_party/boringssl/ssl/libssl.a) |
||||||
|
|
||||||
|
set(GRPC_PROTO_GENS_DIR ${CMAKE_BINARY_DIR}/gens) |
||||||
|
file(MAKE_DIRECTORY ${GRPC_PROTO_GENS_DIR}) |
||||||
|
include_directories(${GRPC_PROTO_GENS_DIR}) |
||||||
|
|
||||||
|
function(android_protobuf_grpc_generate_cpp SRC_FILES HDR_FILES INCLUDE_ROOT) |
||||||
|
if(NOT ARGN) |
||||||
|
message(SEND_ERROR "Error: android_protobuf_grpc_generate_cpp() called without any proto files") |
||||||
|
return() |
||||||
|
endif() |
||||||
|
|
||||||
|
set(${SRC_FILES}) |
||||||
|
set(${HDR_FILES}) |
||||||
|
set(PROTOBUF_INCLUDE_PATH -I ${INCLUDE_ROOT}) |
||||||
|
foreach(FIL ${ARGN}) |
||||||
|
get_filename_component(ABS_FIL ${FIL} ABSOLUTE) |
||||||
|
get_filename_component(FIL_WE ${FIL} NAME_WE) |
||||||
|
file(RELATIVE_PATH REL_FIL ${CMAKE_CURRENT_SOURCE_DIR}/${INCLUDE_ROOT} ${ABS_FIL}) |
||||||
|
get_filename_component(REL_DIR ${REL_FIL} DIRECTORY) |
||||||
|
set(RELFIL_WE "${REL_DIR}/${FIL_WE}") |
||||||
|
|
||||||
|
list(APPEND ${SRC_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.cc") |
||||||
|
list(APPEND ${HDR_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.h") |
||||||
|
list(APPEND ${SRC_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.cc") |
||||||
|
list(APPEND ${HDR_FILES} "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.h") |
||||||
|
|
||||||
|
add_custom_command( |
||||||
|
OUTPUT "${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.cc" |
||||||
|
"${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.grpc.pb.h" |
||||||
|
"${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.cc" |
||||||
|
"${GRPC_PROTO_GENS_DIR}/${RELFIL_WE}.pb.h" |
||||||
|
COMMAND ${helloworld_PROTOBUF_PROTOC_EXECUTABLE} |
||||||
|
ARGS --grpc_out=${GRPC_PROTO_GENS_DIR} |
||||||
|
--cpp_out=${GRPC_PROTO_GENS_DIR} |
||||||
|
--plugin=protoc-gen-grpc=${helloworld_GRPC_CPP_PLUGIN_EXECUTABLE} |
||||||
|
${PROTOBUF_INCLUDE_PATH} |
||||||
|
${REL_FIL} |
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |
||||||
|
DEPENDS ${helloworld_PROTOBUF_PROTOC_EXECUTABLE} ${helloworld_GRPC_CPP_PLUGIN_EXECUTABLE} ${ABS_FIL} ) |
||||||
|
endforeach() |
||||||
|
|
||||||
|
set_source_files_properties(${${SRC_FILES}} ${${HDR_FILES}} PROPERTIES GENERATED TRUE) |
||||||
|
set(${SRC_FILES} ${${SRC_FILES}} PARENT_SCOPE) |
||||||
|
set(${HDR_FILES} ${${HDR_FILES}} PARENT_SCOPE) |
||||||
|
endfunction() |
||||||
|
|
||||||
|
set(PROTO_BASE_DIR ${GRPC_SRC_DIR}/examples/protos) |
||||||
|
|
||||||
|
android_protobuf_grpc_generate_cpp( |
||||||
|
HELLOWORLD_PROTO_SRCS HELLOWORLD_PROTO_HDRS ${PROTO_BASE_DIR} ${PROTO_BASE_DIR}/helloworld.proto) |
||||||
|
|
||||||
|
add_library(helloworld_proto_lib |
||||||
|
SHARED ${HELLOWORLD_PROTO_HDRS} ${HELLOWORLD_PROTO_SRCS}) |
||||||
|
|
||||||
|
target_link_libraries(helloworld_proto_lib |
||||||
|
libprotobuf |
||||||
|
libgrpc++ |
||||||
|
android |
||||||
|
log) |
||||||
|
|
||||||
|
find_library(log-lib |
||||||
|
log) |
||||||
|
|
||||||
|
add_library(grpc-helloworld |
||||||
|
SHARED src/main/cpp/grpc-helloworld.cc) |
||||||
|
|
||||||
|
target_include_directories(grpc-helloworld |
||||||
|
PRIVATE ${HELLOWORLD_PROTO_HEADERS}) |
||||||
|
|
||||||
|
target_link_libraries(grpc-helloworld |
||||||
|
libgrpc++ |
||||||
|
libgrpc |
||||||
|
libzlib |
||||||
|
libcares |
||||||
|
libssl |
||||||
|
libcrypto |
||||||
|
helloworld_proto_lib |
||||||
|
libgpr |
||||||
|
android |
||||||
|
${log-lib}) |
@ -0,0 +1,53 @@ |
|||||||
|
apply plugin: 'com.android.application' |
||||||
|
|
||||||
|
android { |
||||||
|
compileSdkVersion 26 |
||||||
|
defaultConfig { |
||||||
|
applicationId "io.grpc.android.cpp.helloworldexample" |
||||||
|
minSdkVersion 14 |
||||||
|
targetSdkVersion 26 |
||||||
|
versionCode 1 |
||||||
|
versionName "1.0" |
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" |
||||||
|
externalNativeBuild { |
||||||
|
cmake { |
||||||
|
cppFlags "-std=c++14 -frtti -fexceptions" |
||||||
|
arguments '-DANDROID_STL=c++_static' |
||||||
|
arguments '-DRUN_HAVE_POSIX_REGEX=0' |
||||||
|
arguments '-DRUN_HAVE_STD_REGEX=0' |
||||||
|
arguments '-DRUN_HAVE_STEADY_CLOCK=0' |
||||||
|
arguments '-Dprotobuf_BUILD_PROTOC_BINARIES=off' |
||||||
|
arguments '-DgRPC_BUILD_CODEGEN=off' |
||||||
|
// Set this to the path to the protoc binary on the host system (codegen is not |
||||||
|
// cross-compiled to Android) |
||||||
|
arguments '-Dhelloworld_PROTOBUF_PROTOC_EXECUTABLE=/usr/local/bin/protoc' |
||||||
|
// Set this to the path to the gRPC C++ protoc plugin binary on the host system |
||||||
|
// (codegen is not cross-compiled to Android) |
||||||
|
arguments '-Dhelloworld_GRPC_CPP_PLUGIN_EXECUTABLE=/usr/local/bin/grpc_cpp_plugin' |
||||||
|
} |
||||||
|
} |
||||||
|
ndk.abiFilters 'x86' |
||||||
|
} |
||||||
|
buildTypes { |
||||||
|
debug { |
||||||
|
minifyEnabled false |
||||||
|
} |
||||||
|
release { |
||||||
|
minifyEnabled true |
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' |
||||||
|
} |
||||||
|
} |
||||||
|
externalNativeBuild { |
||||||
|
cmake { |
||||||
|
path "CMakeLists.txt" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
dependencies { |
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar']) |
||||||
|
implementation 'com.android.support:appcompat-v7:26.1.0' |
||||||
|
testImplementation 'junit:junit:4.12' |
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.1' |
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
# Add project specific ProGuard rules here. |
||||||
|
# You can control the set of applied configuration files using the |
||||||
|
# proguardFiles setting in build.gradle. |
||||||
|
# |
||||||
|
# For more details, see |
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html |
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following |
||||||
|
# and specify the fully qualified class name to the JavaScript interface |
||||||
|
# class: |
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview { |
||||||
|
# public *; |
||||||
|
#} |
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for |
||||||
|
# debugging stack traces. |
||||||
|
#-keepattributes SourceFile,LineNumberTable |
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to |
||||||
|
# hide the original source file name. |
||||||
|
#-renamesourcefileattribute SourceFile |
@ -0,0 +1,22 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
package="io.grpc.helloworldexample.cpp" > |
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" /> |
||||||
|
|
||||||
|
<application |
||||||
|
android:allowBackup="false" |
||||||
|
android:icon="@mipmap/ic_launcher" |
||||||
|
android:label="@string/app_name" |
||||||
|
android:theme="@style/Base.V7.Theme.AppCompat.Light" > |
||||||
|
<activity |
||||||
|
android:name=".HelloworldActivity" |
||||||
|
android:label="@string/app_name" > |
||||||
|
<intent-filter> |
||||||
|
<action android:name="android.intent.action.MAIN" /> |
||||||
|
<category android:name="android.intent.category.LAUNCHER" /> |
||||||
|
</intent-filter> |
||||||
|
</activity> |
||||||
|
</application> |
||||||
|
|
||||||
|
</manifest> |
@ -0,0 +1,142 @@ |
|||||||
|
/*
|
||||||
|
* |
||||||
|
* Copyright 2018 gRPC 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 |
||||||
|
* |
||||||
|
* http://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. |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <atomic> |
||||||
|
|
||||||
|
#include <grpc++/grpc++.h> |
||||||
|
#include <jni.h> |
||||||
|
|
||||||
|
#include "helloworld.grpc.pb.h" |
||||||
|
|
||||||
|
using grpc::Channel; |
||||||
|
using grpc::ClientContext; |
||||||
|
using grpc::Server; |
||||||
|
using grpc::ServerBuilder; |
||||||
|
using grpc::ServerContext; |
||||||
|
using grpc::Status; |
||||||
|
using helloworld::Greeter; |
||||||
|
using helloworld::HelloReply; |
||||||
|
using helloworld::HelloRequest; |
||||||
|
|
||||||
|
std::atomic<bool> stop_server(false); |
||||||
|
|
||||||
|
// Logic and data behind the server's behavior.
|
||||||
|
class GreeterServiceImpl final : public Greeter::Service { |
||||||
|
Status SayHello(ServerContext* context, const HelloRequest* request, |
||||||
|
HelloReply* reply) override { |
||||||
|
std::string prefix("Hello "); |
||||||
|
reply->set_message(prefix + request->name()); |
||||||
|
return Status::OK; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
void StartServer(JNIEnv* env, jobject obj, jmethodID is_cancelled_mid, |
||||||
|
int port) { |
||||||
|
const int host_port_buf_size = 1024; |
||||||
|
char host_port[host_port_buf_size]; |
||||||
|
snprintf(host_port, host_port_buf_size, "0.0.0.0:%d", port); |
||||||
|
|
||||||
|
GreeterServiceImpl service; |
||||||
|
ServerBuilder builder; |
||||||
|
// Listen on the given address without any authentication mechanism.
|
||||||
|
builder.AddListeningPort(host_port, grpc::InsecureServerCredentials()); |
||||||
|
// Register "service" as the instance through which we'll communicate with
|
||||||
|
// clients. In this case it corresponds to an *synchronous* service.
|
||||||
|
builder.RegisterService(&service); |
||||||
|
// Finally assemble the server.
|
||||||
|
std::unique_ptr<Server> server(builder.BuildAndStart()); |
||||||
|
while (!stop_server.load()) { |
||||||
|
// Check with the Java code to see if the user has requested the server stop or the app is no
|
||||||
|
// longer in the foreground.
|
||||||
|
jboolean is_cancelled = env->CallBooleanMethod(obj, is_cancelled_mid); |
||||||
|
if (is_cancelled == JNI_TRUE) { |
||||||
|
stop_server = true; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class GreeterClient { |
||||||
|
public: |
||||||
|
GreeterClient(std::shared_ptr<Channel> channel) |
||||||
|
: stub_(Greeter::NewStub(channel)) {} |
||||||
|
|
||||||
|
// Assembles the client's payload, sends it and presents the response back
|
||||||
|
// from the server.
|
||||||
|
std::string SayHello(const std::string& user) { |
||||||
|
// Data we are sending to the server.
|
||||||
|
HelloRequest request; |
||||||
|
request.set_name(user); |
||||||
|
|
||||||
|
// Container for the data we expect from the server.
|
||||||
|
HelloReply reply; |
||||||
|
|
||||||
|
// Context for the client. It could be used to convey extra information to
|
||||||
|
// the server and/or tweak certain RPC behaviors.
|
||||||
|
ClientContext context; |
||||||
|
// The actual RPC.
|
||||||
|
Status status = stub_->SayHello(&context, request, &reply); |
||||||
|
|
||||||
|
if (status.ok()) { |
||||||
|
return reply.message(); |
||||||
|
} else { |
||||||
|
return status.error_message(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private: |
||||||
|
std::unique_ptr<Greeter::Stub> stub_; |
||||||
|
}; |
||||||
|
|
||||||
|
// Send an RPC and return the response. Invoked from Java code.
|
||||||
|
extern "C" JNIEXPORT jstring JNICALL |
||||||
|
Java_io_grpc_helloworldexample_cpp_HelloworldActivity_sayHello( |
||||||
|
JNIEnv* env, jobject obj_unused, jstring host_raw, jint port_raw, |
||||||
|
jstring message_raw) { |
||||||
|
const char* host_chars = env->GetStringUTFChars(host_raw, (jboolean*)0); |
||||||
|
std::string host(host_chars, env->GetStringUTFLength(host_raw)); |
||||||
|
|
||||||
|
int port = static_cast<int>(port_raw); |
||||||
|
|
||||||
|
const char* message_chars = env->GetStringUTFChars(message_raw, (jboolean*)0); |
||||||
|
std::string message(message_chars, env->GetStringUTFLength(message_raw)); |
||||||
|
|
||||||
|
const int host_port_buf_size = 1024; |
||||||
|
char host_port[host_port_buf_size]; |
||||||
|
snprintf(host_port, host_port_buf_size, "%s:%d", host.c_str(), port); |
||||||
|
|
||||||
|
GreeterClient greeter( |
||||||
|
grpc::CreateChannel(host_port, grpc::InsecureChannelCredentials())); |
||||||
|
std::string reply = greeter.SayHello(message); |
||||||
|
|
||||||
|
return env->NewStringUTF(reply.c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
// Start the server. Invoked from Java code.
|
||||||
|
extern "C" JNIEXPORT void JNICALL |
||||||
|
Java_io_grpc_helloworldexample_cpp_HelloworldActivity_startServer( |
||||||
|
JNIEnv* env, jobject obj_this, jint port_raw) { |
||||||
|
int port = static_cast<int>(port_raw); |
||||||
|
|
||||||
|
jclass cls = env->GetObjectClass(obj_this); |
||||||
|
jmethodID is_cancelled_mid = |
||||||
|
env->GetMethodID(cls, "isRunServerTaskCancelled", "()Z"); |
||||||
|
|
||||||
|
stop_server = false; |
||||||
|
|
||||||
|
StartServer(env, obj_this, is_cancelled_mid, port); |
||||||
|
} |
@ -0,0 +1,167 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2018, gRPC Authors All rights reserved. |
||||||
|
* |
||||||
|
* 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 |
||||||
|
* |
||||||
|
* http://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. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.grpc.helloworldexample.cpp; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.os.AsyncTask; |
||||||
|
import android.os.Bundle; |
||||||
|
import android.support.v7.app.AppCompatActivity; |
||||||
|
import android.text.TextUtils; |
||||||
|
import android.text.method.ScrollingMovementMethod; |
||||||
|
import android.view.View; |
||||||
|
import android.view.inputmethod.InputMethodManager; |
||||||
|
import android.widget.Button; |
||||||
|
import android.widget.EditText; |
||||||
|
import android.widget.TextView; |
||||||
|
import android.widget.Toast; |
||||||
|
import java.lang.ref.WeakReference; |
||||||
|
|
||||||
|
public class HelloworldActivity extends AppCompatActivity { |
||||||
|
|
||||||
|
static { |
||||||
|
System.loadLibrary("grpc-helloworld"); |
||||||
|
} |
||||||
|
|
||||||
|
private Button sendButton; |
||||||
|
private Button serverButton; |
||||||
|
private EditText hostEdit; |
||||||
|
private EditText portEdit; |
||||||
|
private EditText messageEdit; |
||||||
|
private EditText serverPortEdit; |
||||||
|
private TextView resultText; |
||||||
|
private GrpcTask grpcTask; |
||||||
|
private RunServerTask runServerTask; |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onCreate(Bundle savedInstanceState) { |
||||||
|
super.onCreate(savedInstanceState); |
||||||
|
setContentView(R.layout.activity_helloworld); |
||||||
|
sendButton = (Button) findViewById(R.id.send_button); |
||||||
|
serverButton = (Button) findViewById(R.id.server_button); |
||||||
|
hostEdit = (EditText) findViewById(R.id.host_edit_text); |
||||||
|
portEdit = (EditText) findViewById(R.id.port_edit_text); |
||||||
|
messageEdit = (EditText) findViewById(R.id.message_edit_text); |
||||||
|
serverPortEdit = (EditText) findViewById(R.id.server_port_edit_text); |
||||||
|
resultText = (TextView) findViewById(R.id.grpc_response_text); |
||||||
|
resultText.setMovementMethod(new ScrollingMovementMethod()); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onPause() { |
||||||
|
super.onPause(); |
||||||
|
if (runServerTask != null) { |
||||||
|
runServerTask.cancel(true); |
||||||
|
runServerTask = null; |
||||||
|
serverButton.setText("Start gRPC Server"); |
||||||
|
} |
||||||
|
if (grpcTask != null) { |
||||||
|
grpcTask.cancel(true); |
||||||
|
grpcTask = null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public void sendMessage(View view) { |
||||||
|
((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)) |
||||||
|
.hideSoftInputFromWindow(hostEdit.getWindowToken(), 0); |
||||||
|
sendButton.setEnabled(false); |
||||||
|
resultText.setText(""); |
||||||
|
grpcTask = new GrpcTask(this); |
||||||
|
grpcTask.executeOnExecutor( |
||||||
|
AsyncTask.THREAD_POOL_EXECUTOR, |
||||||
|
hostEdit.getText().toString(), |
||||||
|
messageEdit.getText().toString(), |
||||||
|
portEdit.getText().toString()); |
||||||
|
} |
||||||
|
|
||||||
|
public void startOrStopServer(View view) { |
||||||
|
if (runServerTask != null) { |
||||||
|
runServerTask.cancel(true); |
||||||
|
runServerTask = null; |
||||||
|
serverButton.setText("Start gRPC Server"); |
||||||
|
Toast.makeText(this, "Server stopped", Toast.LENGTH_SHORT).show(); |
||||||
|
} else { |
||||||
|
runServerTask = new RunServerTask(this); |
||||||
|
String portStr = serverPortEdit.getText().toString(); |
||||||
|
int port = TextUtils.isEmpty(portStr) ? 50051 : Integer.valueOf(portStr); |
||||||
|
runServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, port); |
||||||
|
serverButton.setText("Stop gRPC Server"); |
||||||
|
Toast.makeText(this, "Server started on port " + port, Toast.LENGTH_SHORT).show(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class RunServerTask extends AsyncTask<Integer, Void, Void> { |
||||||
|
private final WeakReference<HelloworldActivity> activityReference; |
||||||
|
|
||||||
|
private RunServerTask(HelloworldActivity activity) { |
||||||
|
this.activityReference = new WeakReference<HelloworldActivity>(activity); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected Void doInBackground(Integer... params) { |
||||||
|
int port = params[0]; |
||||||
|
HelloworldActivity activity = activityReference.get(); |
||||||
|
if (activity != null) { |
||||||
|
activity.startServer(port); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static class GrpcTask extends AsyncTask<String, Void, String> { |
||||||
|
private final WeakReference<HelloworldActivity> activityReference; |
||||||
|
|
||||||
|
private GrpcTask(HelloworldActivity activity) { |
||||||
|
this.activityReference = new WeakReference<HelloworldActivity>(activity); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected String doInBackground(String... params) { |
||||||
|
String host = params[0]; |
||||||
|
String message = params[1]; |
||||||
|
String portStr = params[2]; |
||||||
|
int port = TextUtils.isEmpty(portStr) ? 50051 : Integer.valueOf(portStr); |
||||||
|
return sayHello(host, port, message); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
protected void onPostExecute(String result) { |
||||||
|
HelloworldActivity activity = activityReference.get(); |
||||||
|
if (activity == null || isCancelled()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
TextView resultText = (TextView) activity.findViewById(R.id.grpc_response_text); |
||||||
|
Button sendButton = (Button) activity.findViewById(R.id.send_button); |
||||||
|
resultText.setText(result); |
||||||
|
sendButton.setEnabled(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Invoked by native code to stop server when RunServerTask has been canceled, either by user |
||||||
|
* request or upon app moving to background. |
||||||
|
*/ |
||||||
|
public boolean isRunServerTaskCancelled() { |
||||||
|
if (runServerTask != null) { |
||||||
|
return runServerTask.isCancelled(); |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public static native String sayHello(String host, int port, String message); |
||||||
|
|
||||||
|
public native void startServer(int port); |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||||
|
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" |
||||||
|
android:layout_height="match_parent" |
||||||
|
tools:context=".HelloworldActivity" |
||||||
|
android:orientation="vertical" > |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:paddingTop="12dp" |
||||||
|
android:paddingBottom="12dp" |
||||||
|
android:textSize="16sp" |
||||||
|
android:text="gRPC Client Configuration" |
||||||
|
android:textStyle="bold" /> |
||||||
|
|
||||||
|
<LinearLayout |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:orientation="horizontal"> |
||||||
|
<EditText |
||||||
|
android:id="@+id/host_edit_text" |
||||||
|
android:layout_weight="2" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:hint="Enter Host" /> |
||||||
|
<EditText |
||||||
|
android:id="@+id/port_edit_text" |
||||||
|
android:layout_weight="1" |
||||||
|
android:layout_width="0dp" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:inputType="numberDecimal" |
||||||
|
android:hint="Enter Port" /> |
||||||
|
</LinearLayout> |
||||||
|
|
||||||
|
|
||||||
|
<EditText |
||||||
|
android:id="@+id/message_edit_text" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:hint="Enter message to send" /> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/send_button" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:onClick="sendMessage" |
||||||
|
android:text="Send gRPC Request" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:paddingTop="12dp" |
||||||
|
android:paddingBottom="12dp" |
||||||
|
android:textSize="16sp" |
||||||
|
android:text="Response:" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:id="@+id/grpc_response_text" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:scrollbars = "vertical" |
||||||
|
android:textSize="16sp" /> |
||||||
|
|
||||||
|
<TextView |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:paddingTop="12dp" |
||||||
|
android:paddingBottom="12dp" |
||||||
|
android:textSize="16sp" |
||||||
|
android:text="gRPC Server Configuration" |
||||||
|
android:textStyle="bold" /> |
||||||
|
|
||||||
|
<EditText |
||||||
|
android:id="@+id/server_port_edit_text" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:hint="Server port" /> |
||||||
|
|
||||||
|
<Button |
||||||
|
android:id="@+id/server_button" |
||||||
|
android:layout_width="match_parent" |
||||||
|
android:layout_height="wrap_content" |
||||||
|
android:onClick="startOrStopServer" |
||||||
|
android:text="Start gRPC Server" /> |
||||||
|
|
||||||
|
</LinearLayout> |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 7.5 KiB |
@ -0,0 +1,3 @@ |
|||||||
|
<resources> |
||||||
|
<string name="app_name">GrpcHelloworldCppExample</string> |
||||||
|
</resources> |
@ -0,0 +1,24 @@ |
|||||||
|
// Top-level build file where you can add configuration options common to all sub-projects/modules. |
||||||
|
|
||||||
|
buildscript { |
||||||
|
repositories { |
||||||
|
google() |
||||||
|
jcenter() |
||||||
|
} |
||||||
|
dependencies { |
||||||
|
classpath 'com.android.tools.build:gradle:3.0.1' |
||||||
|
// NOTE: Do not place your application dependencies here; they belong |
||||||
|
// in the individual module build.gradle files |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
allprojects { |
||||||
|
repositories { |
||||||
|
google() |
||||||
|
jcenter() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
task clean(type: Delete) { |
||||||
|
delete rootProject.buildDir |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
# Project-wide Gradle settings. |
||||||
|
|
||||||
|
# IDE (e.g. Android Studio) users: |
||||||
|
# Gradle settings configured through the IDE *will override* |
||||||
|
# any settings specified in this file. |
||||||
|
|
||||||
|
# For more details on how to configure your build environment visit |
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html |
||||||
|
|
||||||
|
# Specifies the JVM arguments used for the daemon process. |
||||||
|
# The setting is particularly useful for tweaking memory settings. |
||||||
|
org.gradle.jvmargs=-Xmx1536m |
||||||
|
|
||||||
|
# When configured, Gradle will run in incubating parallel mode. |
||||||
|
# This option should only be used with decoupled projects. More details, visit |
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects |
||||||
|
# org.gradle.parallel=true |
Binary file not shown.
@ -0,0 +1,6 @@ |
|||||||
|
#Thu Jan 25 11:45:30 PST 2018 |
||||||
|
distributionBase=GRADLE_USER_HOME |
||||||
|
distributionPath=wrapper/dists |
||||||
|
zipStoreBase=GRADLE_USER_HOME |
||||||
|
zipStorePath=wrapper/dists |
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip |
@ -0,0 +1,160 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
|
||||||
|
############################################################################## |
||||||
|
## |
||||||
|
## Gradle start up script for UN*X |
||||||
|
## |
||||||
|
############################################################################## |
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||||
|
DEFAULT_JVM_OPTS="" |
||||||
|
|
||||||
|
APP_NAME="Gradle" |
||||||
|
APP_BASE_NAME=`basename "$0"` |
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value. |
||||||
|
MAX_FD="maximum" |
||||||
|
|
||||||
|
warn ( ) { |
||||||
|
echo "$*" |
||||||
|
} |
||||||
|
|
||||||
|
die ( ) { |
||||||
|
echo |
||||||
|
echo "$*" |
||||||
|
echo |
||||||
|
exit 1 |
||||||
|
} |
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false'). |
||||||
|
cygwin=false |
||||||
|
msys=false |
||||||
|
darwin=false |
||||||
|
case "`uname`" in |
||||||
|
CYGWIN* ) |
||||||
|
cygwin=true |
||||||
|
;; |
||||||
|
Darwin* ) |
||||||
|
darwin=true |
||||||
|
;; |
||||||
|
MINGW* ) |
||||||
|
msys=true |
||||||
|
;; |
||||||
|
esac |
||||||
|
|
||||||
|
# Attempt to set APP_HOME |
||||||
|
# Resolve links: $0 may be a link |
||||||
|
PRG="$0" |
||||||
|
# Need this for relative symlinks. |
||||||
|
while [ -h "$PRG" ] ; do |
||||||
|
ls=`ls -ld "$PRG"` |
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||||
|
if expr "$link" : '/.*' > /dev/null; then |
||||||
|
PRG="$link" |
||||||
|
else |
||||||
|
PRG=`dirname "$PRG"`"/$link" |
||||||
|
fi |
||||||
|
done |
||||||
|
SAVED="`pwd`" |
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null |
||||||
|
APP_HOME="`pwd -P`" |
||||||
|
cd "$SAVED" >/dev/null |
||||||
|
|
||||||
|
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" |
||||||
|
which java >/dev/null 2>&1 || 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 |
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can. |
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then |
||||||
|
MAX_FD_LIMIT=`ulimit -H -n` |
||||||
|
if [ $? -eq 0 ] ; then |
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
||||||
|
MAX_FD="$MAX_FD_LIMIT" |
||||||
|
fi |
||||||
|
ulimit -n $MAX_FD |
||||||
|
if [ $? -ne 0 ] ; then |
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD" |
||||||
|
fi |
||||||
|
else |
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
||||||
|
fi |
||||||
|
fi |
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock |
||||||
|
if $darwin; then |
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
||||||
|
fi |
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java |
||||||
|
if $cygwin ; then |
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"` |
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath |
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
||||||
|
SEP="" |
||||||
|
for dir in $ROOTDIRSRAW ; do |
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir" |
||||||
|
SEP="|" |
||||||
|
done |
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))" |
||||||
|
# Add a user-defined pattern to the cygpath arguments |
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
||||||
|
fi |
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
||||||
|
i=0 |
||||||
|
for arg in "$@" ; do |
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
||||||
|
else |
||||||
|
eval `echo args$i`="\"$arg\"" |
||||||
|
fi |
||||||
|
i=$((i+1)) |
||||||
|
done |
||||||
|
case $i in |
||||||
|
(0) set -- ;; |
||||||
|
(1) set -- "$args0" ;; |
||||||
|
(2) set -- "$args0" "$args1" ;; |
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;; |
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
||||||
|
esac |
||||||
|
fi |
||||||
|
|
||||||
|
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules |
||||||
|
function splitJvmOpts() { |
||||||
|
JVM_OPTS=("$@") |
||||||
|
} |
||||||
|
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS |
||||||
|
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" |
||||||
|
|
||||||
|
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" |
@ -0,0 +1,90 @@ |
|||||||
|
@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 |
||||||
|
|
||||||
|
@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= |
||||||
|
|
||||||
|
set DIRNAME=%~dp0 |
||||||
|
if "%DIRNAME%" == "" set DIRNAME=. |
||||||
|
set APP_BASE_NAME=%~n0 |
||||||
|
set APP_HOME=%DIRNAME% |
||||||
|
|
||||||
|
@rem Find java.exe |
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome |
||||||
|
|
||||||
|
set JAVA_EXE=java.exe |
||||||
|
%JAVA_EXE% -version >NUL 2>&1 |
||||||
|
if "%ERRORLEVEL%" == "0" goto init |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
goto fail |
||||||
|
|
||||||
|
:findJavaFromJavaHome |
||||||
|
set JAVA_HOME=%JAVA_HOME:"=% |
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init |
||||||
|
|
||||||
|
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. |
||||||
|
|
||||||
|
goto fail |
||||||
|
|
||||||
|
:init |
||||||
|
@rem Get command-line arguments, handling Windowz variants |
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||||
|
if "%@eval[2+2]" == "4" goto 4NT_args |
||||||
|
|
||||||
|
:win9xME_args |
||||||
|
@rem Slurp the command line arguments. |
||||||
|
set CMD_LINE_ARGS= |
||||||
|
set _SKIP=2 |
||||||
|
|
||||||
|
:win9xME_args_slurp |
||||||
|
if "x%~1" == "x" goto execute |
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%* |
||||||
|
goto execute |
||||||
|
|
||||||
|
:4NT_args |
||||||
|
@rem Get arguments from the 4NT Shell from JP Software |
||||||
|
set CMD_LINE_ARGS=%$ |
||||||
|
|
||||||
|
: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 %CMD_LINE_ARGS% |
||||||
|
|
||||||
|
:end |
||||||
|
@rem End local scope for the variables with windows NT shell |
||||||
|
if "%ERRORLEVEL%"=="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 |
||||||
|
|
||||||
|
:mainEnd |
||||||
|
if "%OS%"=="Windows_NT" endlocal |
||||||
|
|
||||||
|
:omega |
@ -0,0 +1 @@ |
|||||||
|
include ':app' |
Loading…
Reference in new issue