Executorch as a native c++ lib in Android

mads-oestergaard: <p>Hi,<br>
I’m trying to build an application that uses executorch with oboe in Android. As oboe is a native c++ lib in android, it makes sense to use the c++ version of executorch, do all the processing in c++ and then have a common jni layer that exposes the necessary handles.</p>
<p>Its just that cmake and linking is tricky <img src="https://discuss.pytorch.org/images/emoji/apple/blush.png?v=12" title=":blush:" class="emoji" alt=":blush:" loading="lazy" width="20" height="20"></p>
<p>I’ve tried the following:</p>
<ol>
<li>cross-compiling executorch on a linux system following <a href="https://pytorch.org/executorch/0.3/demo-apps-android.html#xnnpack" rel="noopener nofollow ugc">step one from the android guide</a></li>
<li>create a new android project with native lib support (in macos with intel cpu – hence the need for a linux compile due to torch versions)</li>
<li>copy cmake-android-out into src/main/executorch in the android project</li>
<li>copy Utils.cmake to src/main/executorch/build</li>
<li>modify CMakeList.txt in src/main/cpp to contain the following:</li>
</ol>
<pre><code class="lang-auto"># For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("test-project")

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
        # List C/C++ source files with relative paths to this CMakeLists.txt.
        native-lib.cpp
        jni_bridge.cpp)

find_package (oboe REQUIRED CONFIG)

set(EXECUTORCH_ROOT "${CMAKE_CURRENT_LIST_DIR}/../..")
set(_common_include_directories ${EXECUTORCH_ROOT}/executorch)

include(${EXECUTORCH_ROOT}/executorch/build/Utils.cmake)
set( executorch_DIR "${EXECUTORCH_ROOT}/executorch/lib/cmake/ExecuTorch/")
find_package (executorch REQUIRED)
include_directories( "${EXECUTORCH_ROOT}/executorch/include" )
target_link_options_shared_lib(executorch)

set(link_libraries)
list(APPEND link_libraries extension_data_loader extension_module executorch)
list(APPEND link_libraries portable_ops_lib portable_kernels)
target_link_options_shared_lib(portable_ops_lib)
target_link_options_shared_lib(xnnpack_backend)

list(APPEND link_libraries xnnpack_backend XNNPACK pthreadpool cpuinfo)
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${_common_include_directories}
)

#add_library(executorch_no_prim_ops STATIC IMPORTED)
#set_target_properties(executorch_no_prim_ops PROPERTIES IMPORTED_LOCATION "${_root}/executorch/libexecutorch_no_prim_ops.a")

# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        oboe::oboe
        executorch
        log)
</code></pre>
<p>Some of the steps are copied from <a href="https://github.com/pytorch/executorch/blob/v0.3.0/extension/android/CMakeLists.txt" class="inline-onebox" rel="noopener nofollow ugc">executorch/extension/android/CMakeLists.txt at v0.3.0 · pytorch/executorch · GitHub</a>.</p>
<p>With this process, I can build the android app, but it seems that I don’t have access to all the headers - trying to <span class="hashtag-raw">#include</span> &lt;executorch/extension/module/module.h&gt; in a header file results in all kinds of errors when building the app.</p>
<p>Any thoughts about how I should go about using executorch as a c++ lib in android?</p>

hsz: <p>Hi,</p>
<p>Maybe try replace</p>
<pre><code class="lang-auto">include_directories( "${EXECUTORCH_ROOT}/executorch/include" )
</code></pre>
<p>with</p>
<pre><code class="lang-auto">include_directories( "${EXECUTORCH_ROOT}/.." )

</code></pre>
<p>Does it help?</p>
<p>Also feel free to open an issue in <a href="https://github.com/pytorch/executorch/issues" class="inline-onebox" rel="noopener nofollow ugc">Issues · pytorch/executorch · GitHub</a></p>

larryliu0820: <p>Hi, it seems like you need to do:</p>
<pre><code class="lang-auto">include_directories( "{EXECUTORCH_ROOT}/../)
</code></pre>
<p>This is a bit hacky but it enables you to do <code>#include &lt;executorch/...&gt;</code></p>

mads-oestergaard: <p>I sorted it out, so let me just try to write out what ended up working. In the meantime I’ve gotten my hands on a ARM-cpu mac, which helped a lot.</p>
<p>I added executorch as a git sub-module in my android project, giving a project structure like this:</p>
<pre><code class="lang-auto">awesome-app
└── src
    └── main
        └── cpp
            ├── CMakeLists.txt
            ├── audiolib.cpp
            ├── audiolib.h
            ├── executorch_audiolib.cpp # JNI bridge
            └── libs
                ├── build_executorch.sh
                └── executorch
</code></pre>
<p><code>build_exexutorch.sh</code> then installs executorch requirements and compiles for android like this:</p>
<pre data-code-wrap="shell"><code class="lang-shell">cd executorch || exit

git submodule sync
git submodule update --init --recursive

rm -rf cmake-out pip-out

uv venv ../.venv --seed --python 3.11
source ../.venv/bin/activate

./install_requirements.sh

export ANDROID_ABI=arm64-v8a
android_path="/Users/$(whoami)/Library/Android/sdk/ndk/27.0.12077973" || false
export ANDROID_NDK=$android_path

rm -rf cmake-android-out &amp;&amp; mkdir cmake-android-out
cmake . -DCMAKE_INSTALL_PREFIX=cmake-android-out \
  -DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK}/build/cmake/android.toolchain.cmake" \
  -DANDROID_ABI="${ANDROID_ABI}" \
  -DEXECUTORCH_BUILD_XNNPACK=ON \
  -DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \
  -DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
  -DEXECUTORCH_ENABLE_EVENT_TRACER=ON \
  -DEXECUTORCH_BUILD_DEVTOOLS=ON \
  -DEXECUTORCH_BUILD_FLATC=ON \
  -DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \
  -DEXECUTORCH_BUILD_EXTENSION_RUNNER_UTIL=ON \
  -DEXECUTORCH_BUILD_EXTENSION_TENSOR=ON \
  -DEXECUTORCH_ENABLE_LOGGING=ON \
  -DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \
  -DBUILD_EXECUTORCH_PORTABLE_OPS=ON \
  -DEXECUTORCH_ENABLE_PROGRAM_VERIFICATION=ON\
  -DCMAKE_BUILD_TYPE=Release \
  -Bcmake-android-out

cmake --build cmake-android-out -j16 --target install --config Release
</code></pre>
<p>and I’m linking with my audiolib like this:</p>
<pre data-code-wrap="cmake"><code class="lang-cmake"># Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

project("awesome-app")

if(NOT CMAKE_CXX_STANDARD)
    set(CMAKE_CXX_STANDARD 17)
endif()

set(CMAKE_BUILD_TYPE "RELEASE")

add_library(${CMAKE_PROJECT_NAME} SHARED
        executorch_audiolib.cpp # jni-bridge
        audiolib.cpp
)

find_package (oboe REQUIRED CONFIG)

set(_common_compile_options -Wno-deprecated-declarations -fPIC)
set(EXECUTORCH_ROOT "${CMAKE_CURRENT_LIST_DIR}/libs/executorch/cmake-android-out")

include(${EXECUTORCH_ROOT}/../build/Utils.cmake)
set( executorch_DIR "${EXECUTORCH_ROOT}/lib/cmake/ExecuTorch/")
find_package (executorch REQUIRED)
include_directories( "${EXECUTORCH_ROOT}/include" )
include_directories( "${EXECUTORCH_ROOT}/../.." )
target_link_options_shared_lib(executorch)

set(link_libraries)
list(
    APPEND
    link_libraries
    executorch
    extension_data_loader
    extension_module
    extension_runner_util
    extension_tensor
    extension_threadpool
    etdump                                     
    flatccrt                                                        
)

... # target linking, see extension/android cmake file 

target_link_libraries(${CMAKE_PROJECT_NAME}
        # List libraries link to the target library
        android
        oboe::oboe
        executorch
        ${link_libraries}
        log)
</code></pre>