diff --git a/.gitignore b/.gitignore index 453f0c800..309f23209 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ out/ local.properties **.iml *.hprof +.cxx +**/sentry-native-local diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..fe6c3b7cc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "sentry-android-ndk/sentry-native"] + path = sentry-android-ndk/sentry-native + url = https://github.com/getsentry/sentry-native diff --git a/README.md b/README.md index d38ec4450..4138b3e94 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,14 @@ Android SDK for Sentry [![AppVeyor](https://ci.appveyor.com/api/projects/status/kr49snupeb1dsgwa/branch/master?svg=true)](https://ci.appveyor.com/project/sentry/sentry-android/branch/master) [![Tests](https://img.shields.io/appveyor/tests/sentry/sentry-android/master?compact_message)](https://ci.appveyor.com/project/sentry/sentry-android/branch/master/tests) [![codecov](https://codecov.io/gh/getsentry/sentry-android/branch/master/graph/badge.svg)](https://codecov.io/gh/getsentry/sentry-android) + +# Development + +This repository includes [`sentry-native`](https://github.com/getsentry/sentry-native/) as a git submodule. +To build against `sentry-native` checked-out elsewhere in your file system, create a symlink `sentry-android-ndk/sentry-native-local` that points to your `sentry-native` directory. +For example, if you had `sentry-native` checked-out in a sibling directory to this repo: + +`ln -s ../../sentry-native sentry-android-ndk/sentry-native-local` + +which will be picked up by `gradle` and used instead of the git submodule. +This directory is also included in `.gitignore` not to be shown as pending changes. diff --git a/buildSrc/src/main/java/Config.kt b/buildSrc/src/main/java/Config.kt index c2eb39462..1aba8992e 100644 --- a/buildSrc/src/main/java/Config.kt +++ b/buildSrc/src/main/java/Config.kt @@ -12,6 +12,7 @@ object Config { val buildToolsVersion = "29.0.2" val minSdkVersion = 14 + val minSdkVersionNdk = 21 val minSdkVersionDebug = 21 val targetSdkVersion = sdkVersion val compileSdkVersion = sdkVersion diff --git a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java index cf3c001d3..cd5a0ecda 100644 --- a/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java +++ b/sentry-android-core/src/main/java/io/sentry/android/core/AndroidOptionsInitializer.java @@ -2,8 +2,10 @@ import android.content.Context; import io.sentry.core.ILogger; +import io.sentry.core.SentryLevel; import io.sentry.core.SentryOptions; import java.io.File; +import java.lang.reflect.Method; class AndroidOptionsInitializer { static void init(SentryOptions options, Context context) { @@ -19,6 +21,29 @@ static void init(SentryOptions options, Context context, ILogger logger) { ManifestMetadataReader.applyMetadata(context, options); createsEnvelopeDirPath(options, context); + + if (options.isEnableNdk()) { + try { + // TODO: Create Integrations interface and use that to initialize NDK + Class cls = Class.forName("io.sentry.android.ndk.SentryNdk"); + + // TODO: temporary hack + String cacheDirPath = context.getCacheDir().getAbsolutePath() + "/sentry-envelopes"; + File f = new File(cacheDirPath); + f.mkdirs(); + + Method method = cls.getMethod("init", SentryOptions.class, String.class); + Object[] args = new Object[2]; + args[0] = options; + args[1] = cacheDirPath; + method.invoke(null, args); + } catch (ClassNotFoundException exc) { + options.getLogger().log(SentryLevel.ERROR, "Failed to load SentryNdk."); + } catch (Exception e) { + options.getLogger().log(SentryLevel.ERROR, "Failed to initialize SentryNdk.", e); + } + } + options.addEventProcessor(new DefaultAndroidEventProcessor(context, options)); options.setSerializer(new AndroidSerializer(options.getLogger())); } diff --git a/sentry-android-ndk/CMakeLists.txt b/sentry-android-ndk/CMakeLists.txt new file mode 100644 index 000000000..02e736c02 --- /dev/null +++ b/sentry-android-ndk/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.4.1) +project("sentry-android") + +# Adding sentry-native submodule subdirectory +add_subdirectory(${SENTRY_NATIVE_SRC}) + +# sentry-native submodule include path. +include_directories(${SENTRY_NATIVE_SRC}/include) + +# sentry-native source code. +file(GLOB SENTRY_ANDROID_SOURCES ${CMAKE_SOURCE_DIR}/src/main/jni/*.c) + +# Adding dynamic library and linking it with log and sentry-native. +add_library("sentry-android" SHARED ${SENTRY_ANDROID_SOURCES}) +find_library(LOG_LIB log) +target_link_libraries("sentry-android" "sentry" ${LOG_LIB}) diff --git a/sentry-android-ndk/build.gradle.kts b/sentry-android-ndk/build.gradle.kts new file mode 100644 index 000000000..0291db47e --- /dev/null +++ b/sentry-android-ndk/build.gradle.kts @@ -0,0 +1,65 @@ + +plugins { + id("com.android.library") +} + +android { + compileSdkVersion(Config.Android.compileSdkVersion) + buildToolsVersion(Config.Android.buildToolsVersion) + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + defaultConfig { + targetSdkVersion(Config.Android.targetSdkVersion) + javaCompileOptions { + annotationProcessorOptions { + includeCompileClasspath = true + } + } + + minSdkVersion(Config.Android.minSdkVersionNdk) + externalNativeBuild { + val sentryNativeSrc = if (File("${project.projectDir}/sentry-native-local").exists()) { + "sentry-native-local" + } else { + "sentry-native" + } + cmake { + arguments.add(0, "-DANDROID_STL=c++_static") + arguments.add(0, "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON") + arguments.add(0, "-DSENTRY_NATIVE_SRC=" + sentryNativeSrc) + } + } + ndk { + val platform = System.getenv("ABI") + if (platform == null || platform.toLowerCase() == "all") { + abiFilters("x86", "armeabi-v7a", "x86_64", "arm64-v8a") + } else { + abiFilters(platform) + } + } + + missingDimensionStrategy(Config.Flavors.dimension, Config.Flavors.production) + } + + externalNativeBuild { + cmake { + setPath("CMakeLists.txt") + } + } +} + +dependencies { + api(project(":sentry-core")) + api(project(":sentry-android-core")) +} + +val initNative = tasks.register("initNative") { + logger.log(LogLevel.LIFECYCLE, "Initializing git submodules") + commandLine("git", "submodule", "update", "--init", "--recursive") +} + +tasks.named("preBuild") { + dependsOn(initNative) +} diff --git a/sentry-android-ndk/sentry-native b/sentry-android-ndk/sentry-native new file mode 160000 index 000000000..03b86e7d5 --- /dev/null +++ b/sentry-android-ndk/sentry-native @@ -0,0 +1 @@ +Subproject commit 03b86e7d51dafdaa74e034bbd11746bcf6708fc6 diff --git a/sentry-android-ndk/src/main/AndroidManifest.xml b/sentry-android-ndk/src/main/AndroidManifest.xml new file mode 100644 index 000000000..b49a5e7cf --- /dev/null +++ b/sentry-android-ndk/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java new file mode 100644 index 000000000..4e9a273bf --- /dev/null +++ b/sentry-android-ndk/src/main/java/io/sentry/android/ndk/SentryNdk.java @@ -0,0 +1,24 @@ +package io.sentry.android.ndk; + +import io.sentry.core.SentryOptions; + +public class SentryNdk { + static { + System.loadLibrary("sentry"); + } + + static { + System.loadLibrary("sentry-android"); + } + + private static native void initSentryNative(String cacheDirPath); + + public static void notifyNewSerializedEnvelope(String path) { + System.out.println("envelope written to " + path); + } + + public static void init(SentryOptions options, String cacheDirPath) { + // Java_example + initSentryNative(cacheDirPath); + } +} diff --git a/sentry-android-ndk/src/main/jni/sentry.c b/sentry-android-ndk/src/main/jni/sentry.c new file mode 100644 index 000000000..5ab4f192b --- /dev/null +++ b/sentry-android-ndk/src/main/jni/sentry.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include + +struct transport_options { + jmethodID notify_envelope_mid; + jclass cls; + JNIEnv *env; + char event_path[4096]; +}; + +struct transport_options g_transport_options; + +static void send_envelope(const sentry_envelope_t *envelope, void *data) { + char event_path[4096]; + strcpy(event_path, g_transport_options.event_path); + strcat(event_path, "/test.envelope"); + sentry_envelope_write_to_file(envelope, event_path); + jstring jevent_path = (*g_transport_options.env)->NewStringUTF(g_transport_options.env, event_path); + (*g_transport_options.env)->CallStaticVoidMethod(g_transport_options.env, g_transport_options.cls, g_transport_options.notify_envelope_mid, jevent_path); +} + +JNIEXPORT void JNICALL Java_io_sentry_android_ndk_SentryNdk_initSentryNative(JNIEnv *env, jclass cls, jstring cache_dir_path) { + const char *path = (*env)->GetStringUTFChars(env, cache_dir_path, 0); + strcpy(g_transport_options.event_path, path); + g_transport_options.env = env; + g_transport_options.cls = cls; + g_transport_options.notify_envelope_mid = (*env)->GetStaticMethodID(env, cls, "notifyNewSerializedEnvelope", "(Ljava/lang/String;)V"); + + sentry_options_t *options = sentry_options_new(); + + sentry_options_set_transport(options, send_envelope, NULL); + + sentry_options_set_environment(options, "Production"); + sentry_options_set_release(options, "5fd7a6cd"); + sentry_options_set_debug(options, 1); + sentry_options_set_dsn(options, "http://dfbecfd398754c73b6e8104538e89124@sentry.io/1322857"); + + sentry_init(options); + + sentry_value_t event = sentry_value_new_event(); + sentry_value_set_by_key(event, "message", + sentry_value_new_string("Hello World!")); + + sentry_capture_event(event); + + sentry_shutdown(); + +} + diff --git a/sentry-android/build.gradle.kts b/sentry-android/build.gradle.kts index ae7a90292..b57a58888 100644 --- a/sentry-android/build.gradle.kts +++ b/sentry-android/build.gradle.kts @@ -8,7 +8,7 @@ android { defaultConfig { targetSdkVersion(Config.Android.targetSdkVersion) - minSdkVersion(Config.Android.minSdkVersion) + minSdkVersion(Config.Android.minSdkVersionNdk) } compileOptions { @@ -26,5 +26,5 @@ android { dependencies { api(project(":sentry-android-core")) - // TODO: Add NDK: api(project(":sentry-android-ndk")) + api(project(":sentry-android-ndk")) } diff --git a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java b/sentry-core/src/main/java/io/sentry/core/SentryOptions.java index 983b01b4d..a675e4cb2 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryOptions.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryOptions.java @@ -13,6 +13,7 @@ public class SentryOptions { private String dsn; private long shutdownTimeoutMills; private boolean debug; + private boolean enableNdk = true; private @NonNull ILogger logger = NoOpLogger.getInstance(); private SentryLevel diagnosticLevel = DEFAULT_DIAGNOSTIC_LEVEL; private ISerializer serializer; @@ -79,6 +80,14 @@ public void setSerializer(ISerializer serializer) { this.serializer = serializer; } + public boolean isEnableNdk() { + return enableNdk; + } + + public void setEnableNdk(boolean enableNdk) { + this.enableNdk = enableNdk; + } + public long getShutdownTimeout() { return shutdownTimeoutMills; } diff --git a/sentry-sample/build.gradle.kts b/sentry-sample/build.gradle.kts index a78ae11f9..836b60a7f 100644 --- a/sentry-sample/build.gradle.kts +++ b/sentry-sample/build.gradle.kts @@ -11,7 +11,7 @@ android { defaultConfig { applicationId = "io.sentry.sample" - minSdkVersion(Config.Android.minSdkVersion) + minSdkVersion(Config.Android.minSdkVersionNdk) // NDK requires a higher API level than core. targetSdkVersion(Config.Android.targetSdkVersion) versionCode = 1 versionName = "1.0" diff --git a/sentry-sample/src/main/AndroidManifest.xml b/sentry-sample/src/main/AndroidManifest.xml index b62a74193..c93e5dcfe 100644 --- a/sentry-sample/src/main/AndroidManifest.xml +++ b/sentry-sample/src/main/AndroidManifest.xml @@ -24,5 +24,4 @@ - diff --git a/settings.gradle.kts b/settings.gradle.kts index e19891847..89fb17b60 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ rootProject.name = "sentry" rootProject.buildFileName = "build.gradle.kts" -include("sentry-android", "sentry-android-core", "sentry-core", "sentry-sample") +include("sentry-android", "sentry-android-ndk", "sentry-android-core", "sentry-core", "sentry-sample")