Skip to content

Commit 9d3dfbd

Browse files
committed
wip: android native build for OTEL
1 parent 72c6e00 commit 9d3dfbd

File tree

12 files changed

+234
-153
lines changed

12 files changed

+234
-153
lines changed

examples/rn-app/android/gradle.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ android.useAndroidX=true
2525
# Use this property to specify which architecture you want to build.
2626
# You can also override it from the CLI using
2727
# ./gradlew <task> -PreactNativeArchitectures=x86_64
28-
reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
28+
# reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
29+
reactNativeArchitectures=arm64-v8a
2930

3031
# Use this property to enable support to the new architecture.
3132
# This will allow you to use TurboModules and the Fabric render in

packages/backend-wrapper-tracy/cpp/backend/TracyOttreliteBackend.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
#include "TracyOttreliteBackend.hpp"
22

3+
// pthread_getname_np is available on Android API >= 26
4+
#if defined(__ANDROID__) && __ANDROID_API__ >= __ANDROID_API_O__
5+
#define ANDROID_HAS_PTHREAD_GETNAME_NP 1
6+
#else
7+
#define ANDROID_HAS_PTHREAD_GETNAME_NP 0
8+
#endif
9+
10+
// if pthread_getname_np and we are on Android, fall back to using prctl(PR_GET_NAME)
11+
#if !ANDROID_HAS_PTHREAD_GETNAME_NP && defined(__ANDROID__)
12+
#include <array>
13+
#include <linux/prctl.h>
14+
#include <sys/prctl.h>
15+
#endif
316
#include <pthread.h>
417
#include <sstream>
518
#include <thread>
@@ -14,10 +27,18 @@ namespace ottrelite::backend::tracy
1427
{
1528
std::optional<std::string> threadName;
1629

30+
#if ANDROID_HAS_PTHREAD_GETNAME_NP || defined(__APPLE__)
1731
char name[128];
1832
pthread_t thread = pthread_self();
1933
int result = pthread_getname_np(thread, name, sizeof(name));
2034
threadName = result == 0 ? std::make_optional(std::string(name)) : std::nullopt;
35+
#elif defined(__ANDROID__)
36+
std::array<char, 16> name{};
37+
threadName = prctl(PR_GET_NAME, name.data(), 0, 0, 0) == -1 ? std::nullopt
38+
: std::make_optional(std::string(name.data()));
39+
#else
40+
threadName = std::nullopt;
41+
#endif
2142

2243
if (threadName.has_value())
2344
{
@@ -43,7 +64,7 @@ namespace ottrelite::backend::tracy
4364
maybeIt = threadIdToSyncApiZones_.insert({thisThreadId, {}}).first;
4465
}
4566

46-
maybeIt->second.push(std::move(tracyCZoneCtx));
67+
maybeIt->second.push(tracyCZoneCtx);
4768
}
4869

4970
logger_.debug() << "beginEvent(" << eventName << ")";
@@ -62,7 +83,8 @@ namespace ottrelite::backend::tracy
6283

6384
if (stack.empty())
6485
{
65-
logger_.error("endEvent() called without a matching beginEvent() for thread ") << std::hash<std::thread::id>()(thisThreadId);
86+
logger_.error("endEvent() called without a matching beginEvent() for thread ")
87+
<< std::hash<std::thread::id>()(thisThreadId);
6688
return;
6789
}
6890

packages/interop-otel/LICENSE

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,10 +421,6 @@ This project uses opentelemetry-cpp library:
421421
See the License for the specific language governing permissions and
422422
limitations under the License.
423423

424-
This project carries a file ./third_party/wrapper/patches/useLocalOTELDeps.patch that
425-
contains changes to opentelemetry-cpp that disable CMake instructions causing the build to fail on iOS
426-
when libraries for the MacOS platform are discovered by CMake instead.
427-
428424
---
429425

430426
This project uses zlib library:

packages/interop-otel/README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ All the features, usage & installation of this package are documented in the [do
1010

1111
For developer reference, this package utilizes the JS OpenTelemetry API as well as opentelemetry-cpp to integrate with OTEL's APIs. The CPP package requires libcurl and protobuf, which in turn depends on openssl, brotli, zlib. This complicates the build process, especially for iOS, where the packages are configured using CocoaPods while the primary build system for the listed C++ packages is CMake.
1212

13-
For integration with opentelemetry-cpp, this project carries a file packages/interop-otel/third_party/wrapper/patches/useLocalOTELDeps.patch that contains changes to opentelemetry-cpp that disable CMake instructions causing the build to fail on iOS when libraries for the MacOS platform are discovered by CMake instead. The patch is applied during the build process to the local clone of opentelemetry-cpp.
14-
1513
### iOS
1614

1715
Moreover, the aforementioned packages need to be compiled from source for the right architecture, using the right toolchain for iOS.

packages/interop-otel/android/build.gradle

Lines changed: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ repositories {
150150
google()
151151
}
152152

153-
154153
dependencies {
155154
// For < 0.71, this will be from the local maven repo
156155
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
@@ -159,8 +158,27 @@ dependencies {
159158

160159
// Add a dependency on NitroModules
161160
implementation project(":react-native-nitro-modules")
161+
162+
// Depend on Ottrelite core
163+
implementation project(":ottrelite_core")
164+
165+
// CURL dependency for OTEL
166+
implementation 'com.android.ndk.thirdparty:curl:7.85.0-beta-1'
167+
168+
// OpenSSL dependency for OTEL
169+
implementation 'com.android.ndk.thirdparty:openssl:1.1.1q-beta-1'
162170
}
163171

172+
def isReleaseBuild = project.hasProperty("buildType") && project.buildType == "release"
173+
def cxxBuildVariantDirName = isReleaseBuild ? "Release" : "Debug"
174+
def thirdPartySourceDir = file("${project.projectDir}/../third_party").absolutePath
175+
def otelSourceDir = "$thirdPartySourceDir/opentelemetry-cpp"
176+
def protobufSourceDir = "$thirdPartySourceDir/third_party/protobuf"
177+
def wrapperRootDir = "$thirdPartySourceDir/wrapper"
178+
def getWrapperBuildDir = { String abi -> "$wrapperRootDir/android/$cxxBuildVariantDirName/$abi" }
179+
def getProtobufBuildDir = { String abi -> "$wrapperRootDir/android/$cxxBuildVariantDirName/$abi/protobuf" }
180+
def getCmakeInstallDir = { String abi -> "$wrapperRootDir/android/install/$abi" }
181+
164182
def prepareOttreliteHeadersTask = tasks.register('prepareOttreliteHeaders', Copy) {
165183
from files(
166184
fileTree('../cpp/include/public/'),
@@ -172,4 +190,126 @@ def prepareOttreliteHeadersTask = tasks.register('prepareOttreliteHeaders', Copy
172190
includeEmptyDirs = false
173191
}
174192

193+
// depend on Ottrelite Core
175194
preBuild.dependsOn(prepareOttreliteHeadersTask)
195+
196+
// TODO: OpenSSL - like buildAndInstallProtobuf
197+
198+
// TODO: Brotli - like buildAndInstallProtobuf
199+
200+
// TODO: Zlib - like buildAndInstallProtobuf
201+
202+
// TODO: CURL - like buildAndInstallProtobuf
203+
204+
// Protobuf
205+
tasks.register("buildAndInstallProtobuf") {
206+
doFirst {
207+
if (!new File(protobufSourceDir, "CMakeLists.txt").exists()) {
208+
println("[@ottrelite/interop-otel] Cloning Protobuf to ${protobufSourceDir}...")
209+
210+
file(protobufSourceDir).mkdirs()
211+
exec {
212+
commandLine "git", "clone", "--depth", "1", "--branch", "v26.1", "--recurse-submodules", "--shallow-submodules", "https://github.com/protocolbuffers/protobuf.git", protobufSourceDir
213+
}
214+
} else {
215+
println("[@ottrelite/interop-otel] OpenTelemetry Protobuf already cloned in ${protobufSourceDir}")
216+
}
217+
}
218+
219+
doLast {
220+
reactNativeArchitectures().each { abi ->
221+
def protobufBuildDir = getProtobufBuildDir(abi)
222+
def cmakeInstallDir = getCmakeInstallDir(abi)
223+
224+
println("[@ottrelite/interop-otel] Building & installing Protobuf artifacts for ABI $abi, this may take a while...")
225+
226+
if (!new File(protobufBuildDir).exists()) {
227+
file(protobufBuildDir).mkdirs()
228+
}
229+
230+
exec {
231+
println("[@ottrelite/interop-otel] Configuring Protobuf for ABI $abi from ${protobufSourceDir} and writing to ${protobufBuildDir}...")
232+
233+
commandLine "cmake", "-S", protobufSourceDir, "-B", protobufBuildDir,
234+
"-DANDROID_ABI=$abi",
235+
"-DANDROID_PLATFORM=${android.defaultConfig.minSdkVersion.apiLevel}",
236+
"-DCMAKE_TOOLCHAIN_FILE=${android.ndkDirectory}/build/cmake/android.toolchain.cmake",
237+
"-DCMAKE_CXX_FLAGS_DEBUG=-g",
238+
"-Dprotobuf_BUILD_TESTS=OFF"
239+
}
240+
241+
println("[@ottrelite/interop-otel] Building for ABI $abi ${protobufBuildDir}...")
242+
243+
exec {
244+
commandLine "cmake", "--build", protobufBuildDir, "--config", isReleaseBuild ? "Release" : "Debug", "-j8"
245+
}
246+
247+
println("[@ottrelite/interop-otel] Installing build artifacts for ABI $abi to ${cmakeInstallDir}...")
248+
249+
exec {
250+
commandLine "cmake", "--install", protobufBuildDir, "--prefix", cmakeInstallDir
251+
}
252+
253+
println("[@ottrelite/interop-otel] Protobuf build & install finished for ABI $abi")
254+
}
255+
}
256+
}
257+
258+
// CPP wrapper + OTEL CPP SDK
259+
tasks.register("buildAndInstallCppWrapper") {
260+
doFirst {
261+
if (!new File(otelSourceDir, "CMakeLists.txt").exists()) {
262+
println("[@ottrelite/interop-otel] Cloning OpenTelemetry C++ SDK to ${otelSourceDir}...")
263+
264+
file(otelSourceDir).mkdirs()
265+
exec {
266+
commandLine "git", "clone", "--depth", "1", "--branch", "v1.22.0", "https://github.com/open-telemetry/opentelemetry-cpp.git", otelSourceDir
267+
}
268+
} else {
269+
println("[@ottrelite/interop-otel] OpenTelemetry C++ SDK already cloned in ${otelSourceDir}")
270+
}
271+
}
272+
273+
doLast {
274+
reactNativeArchitectures().each { abi ->
275+
def wrapperBuildDir = getWrapperBuildDir(abi)
276+
def cmakeInstallDir = getCmakeInstallDir(abi)
277+
278+
println("[@ottrelite/interop-otel] Building & installing artifacts for ABI $abi, this may take a while...")
279+
280+
if (!new File(wrapperBuildDir).exists()) {
281+
file(wrapperBuildDir).mkdirs()
282+
}
283+
284+
exec {
285+
println("[@ottrelite/interop-otel] Configuring for ABI $abi from ${thirdPartySourceDir} and writing to ${wrapperBuildDir}...")
286+
287+
commandLine "cmake", "-S", thirdPartySourceDir, "-B", wrapperBuildDir,
288+
"-DANDROID_ABI=$abi",
289+
"-DANDROID_PLATFORM=${android.defaultConfig.minSdkVersion.apiLevel}",
290+
"-DCMAKE_TOOLCHAIN_FILE=${android.ndkDirectory}/build/cmake/android.toolchain.cmake",
291+
"-DCMAKE_CXX_FLAGS_DEBUG=-g",
292+
"-DProtobuf_DIR=$cmakeInstallDir/lib/cmake/protobuf",
293+
"-Dabsl_DIR=$cmakeInstallDir/lib/cmake/absl",
294+
"-Dutf8_range_DIR=$cmakeInstallDir/lib/cmake/utf8_range"
295+
}
296+
297+
println("[@ottrelite/interop-otel] Building for ABI $abi ${wrapperBuildDir}...")
298+
299+
exec {
300+
commandLine "cmake", "--build", wrapperBuildDir, "--config", isReleaseBuild ? "Release" : "Debug", "-j8"
301+
}
302+
303+
println("[@ottrelite/interop-otel] Installing build artifacts for ABI $abi to ${cmakeInstallDir}...")
304+
305+
exec {
306+
commandLine "cmake", "--install", wrapperBuildDir, "--prefix", cmakeInstallDir
307+
}
308+
309+
println("[@ottrelite/interop-otel] Build & install finished for ABI $abi")
310+
}
311+
}
312+
}
313+
buildAndInstallCppWrapper.dependsOn(buildAndInstallProtobuf)
314+
315+
preBuild.dependsOn(buildAndInstallCppWrapper)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import com.facebook.react.bridge.ReactApplicationContext;
77
import com.facebook.react.module.model.ReactModuleInfoProvider;
88
import com.facebook.react.TurboReactPackage;
9-
import com.margelo.nitro.ottrelite.ReactNativeOttreliteInteropOTELOnLoad;
9+
import com.margelo.nitro.ottrelite.interop.otel.ReactNativeOttreliteInteropOTELOnLoad;
1010

1111
import java.util.HashMap;
1212

packages/interop-otel/cpp/CMakeLists.txt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ file(GLOB_RECURSE COMMON_SOURCES CONFIGURE_DEPENDS
1818
"${CMAKE_CURRENT_LIST_DIR}/src/*.cpp"
1919
)
2020

21-
target_link_libraries(
22-
${PACKAGE_NAME}
23-
${SHARED_LIBRARIES}
24-
)
25-
2621
target_sources(${PACKAGE_NAME} PUBLIC ${COMMON_SOURCES})
2722

2823
target_include_directories(
@@ -34,3 +29,11 @@ target_include_directories(
3429
PUBLIC
3530
${CMAKE_CURRENT_LIST_DIR}/include/public/
3631
)
32+
33+
# Find RN Ottrelite Core package
34+
find_package(ottrelite_core REQUIRED)
35+
36+
target_link_libraries(
37+
${PACKAGE_NAME}
38+
ottrelite_core::ReactNativeOttrelite # <-- Ottrelite core
39+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
protoc
2+
third_party

packages/interop-otel/third_party/CMakeLists.txt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,64 @@
11
cmake_minimum_required(VERSION 4.0)
22
project(ottrelite-otel-wrapper)
33

4+
include(FetchContent)
5+
46
set(CMAKE_EXE_LINKER_FLAGS "-static")
57
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}/install)
68

9+
set(PROTOC_DIR "${CMAKE_BINARY_DIR}/protoc")
10+
set(PROTOC_ZIP "${CMAKE_BINARY_DIR}/protoc.zip")
11+
set(PROTOC_BINARY "${PROTOC_DIR}/bin/protoc")
12+
if(WIN32)
13+
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
14+
set(PROTOC_URL "https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-win64.zip")
15+
else()
16+
set(PROTOC_URL "https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-win32.zip")
17+
endif()
18+
19+
set(PROTOC_BINARY "${PROTOC_DIR}/bin/protoc.exe")
20+
elseif(APPLE)
21+
set(PROTOC_URL "https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-osx-universal_binary.zip")
22+
elseif(UNIX)
23+
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|arm64)$")
24+
set(PROTOC_URL "https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-aarch_64.zip")
25+
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$")
26+
set(PROTOC_URL "https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_64.zip")
27+
else()
28+
set(PROTOC_URL "https://github.com/protocolbuffers/protobuf/releases/download/v21.6/protoc-21.6-linux-x86_32.zip")
29+
endif()
30+
else()
31+
message(FATAL_ERROR "Unsupported host architecture '${CMAKE_SYSTEM_PROCESSOR}'")
32+
endif()
33+
34+
if(NOT EXISTS "${PROTOC_BINARY}")
35+
message(STATUS "⏳ Downloading protoc binary...")
36+
37+
file(DOWNLOAD
38+
"${PROTOC_URL}"
39+
"${PROTOC_ZIP}"
40+
SHOW_PROGRESS
41+
)
42+
43+
file(MAKE_DIRECTORY "${PROTOC_DIR}")
44+
45+
execute_process(
46+
COMMAND ${CMAKE_COMMAND} -E tar xzf "${PROTOC_ZIP}"
47+
WORKING_DIRECTORY "${PROTOC_DIR}"
48+
)
49+
50+
file(REMOVE "${PROTOC_ZIP}")
51+
52+
# make protoc executable
53+
file(CHMOD "${PROTOC_BINARY}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
54+
else()
55+
message(STATUS "➡️ Using existing protoc binary...")
56+
endif()
57+
58+
get_filename_component(PROTOC_BINARY_ABS "${PROTOC_BINARY}" REALPATH)
59+
message(STATUS "ℹ️ Protobuf compiler binary: ${PROTOC_BINARY_ABS}")
60+
set(PROTOBUF_PROTOC_EXECUTABLE "${PROTOC_BINARY_ABS}" CACHE FILEPATH "Path to the protoc compiler")
61+
762
# iOS configuration
863
if (APPLE)
964
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_LIST_DIR}/ios-cmake/ios.toolchain.cmake")

0 commit comments

Comments
 (0)